Appearance
类加载机制
类加载流程
┌─────────────────────────────────────────────────────────────────┐
│ 类加载流程 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 加载 ──▶ 验证 ──▶ 准备 ──▶ 解析 ──▶ 初始化 ──▶ 使用 ──▶ 卸载 │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ 验证阶段 │ │
│ │ 文件格式验证 → 元数据验证 → 字节码验证 → 符号引用验证 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘加载阶段
| 阶段 | 说明 |
|---|---|
| 加载 | 通过类全限定名获取二进制字节流 |
| 链接 | 验证+准备+解析 |
| 初始化 | 执行 static 块和 static 变量赋值 |
类初始化时机
java
// 1. new 实例
new MyClass();
// 2. 访问静态成员
System.out.println(MyClass.count);
// 3. 反射
Class.forName("com.example.MyClass");
// 4. 初始化子类会触发父类
class Parent { static { System.out.println("Parent init"); } }
class Child extends Parent { static { System.out.println("Child init"); } }
new Child(); // 先输出 Parent init
// 5. main 方法所在类
public static void main(String[] args) { }双亲委派模型
┌─────────────────────────────────────────────────────────────────┐
│ 双亲委派模型 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Bootstrap ClassLoader │
│ (C++ 实现,无法访问) │
│ ▲ │
│ │ │
│ Extension ClassLoader │
│ (加载 JDK 扩展包) │
│ ▲ │
│ │ │
│ Application ClassLoader │
│ (加载 classpath 中的类) │
│ ▲ │
│ │ │
│ 自定义 ClassLoader │
│ │
│ 加载请求向上传递,父类无法加载才由子类加载 │
│ │
└─────────────────────────────────────────────────────────────────┘代码实现
java
public class MyClassLoader extends ClassLoader {
@Override
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
// 1. 先检查是否已加载
Class<?> clazz = findLoadedClass(name);
if (clazz != null) {
return clazz;
}
// 2. 委派给父类加载
try {
ClassLoader parent = getParent();
if (parent != null) {
clazz = parent.loadClass(name, false);
} else {
// 父类为 null,委派给 Bootstrap
clazz = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// 父类找不到
}
// 3. 父类无法加载,自己加载
if (clazz == null) {
clazz = findClass(name);
}
return clazz;
}
}双亲委派好处
┌─────────────────────────────────────────────────────────────────┐
│ 双亲委派的好处 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 1. 安全性 │
│ - 核心类(如 java.lang.String)由 Bootstrap 加载 │
│ - 用户无法伪造相同的类名来替换核心类 │
│ │
│ 2. 避免重复加载 │
│ - 父类已加载的类不会被子类重复加载 │
│ │
│ 3. 类加载器命名空间 │
│ - 不同 ClassLoader 加载的类处于不同命名空间 │
│ │
└─────────────────────────────────────────────────────────────────┘打破双亲委派
热部署场景
java
// Tomcat 的 WebappClassLoader 不遵循双亲委派
// 每个 Web 应用有自己的 ClassLoader
// 同一类名可以加载不同版本
public class TomcatClassLoader extends ClassLoader {
@Override
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
// 判断是否由自己加载
if (name.startsWith("com.myapp.")) {
return findClass(name); // 直接自己加载,不委派
}
// 其他类仍走双亲委派
return super.loadClass(name, resolve);
}
}OSGI 模块化
java
// OSGI 中每个 Bundle 有自己的 ClassLoader
// Bundle 之间可以相互依赖和共享类
// 更细粒度的加载控制运行时包
类加载器与命名空间
java
// 同一命名空间内的类是可见的
// 子命名空间对父命名空间可见,反之不行
ClassLoader cl1 = new MyClassLoader();
ClassLoader cl2 = new MyClassLoader();
Class<?> c1 = cl1.loadClass("com.example.Test");
Class<?> c2 = cl2.loadClass("com.example.Test");
// c1 和 c2 来自不同的类加载器
// 在 JVM 中被视为不同的类型
System.out.println(c1 == c2); // false总结
类加载机制核心知识点:
| 知识点 | 面试频率 | 实战重要性 |
|---|---|---|
| 类加载流程 | ⭐⭐⭐⭐ | ⭐⭐⭐ |
| 双亲委派模型 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| 打破双亲委派 | ⭐⭐⭐ | ⭐⭐⭐ |
| 自定义 ClassLoader | ⭐⭐⭐ | ⭐⭐⭐ |
⚠️ 易错点提醒:
- 类的 ClassLoader 决定类的唯一性
- 双亲委派保证了核心类的安全
- Thread.currentThread().getContextClassLoader() 可以获取当前线程的 ClassLoader
- 类初始化顺序:父类 static -> 子类 static -> 父类构造 -> 子类构造
- 类卸载条件非常苛刻,实践中几乎不会发生