Skip to content

类加载机制

类加载流程

┌─────────────────────────────────────────────────────────────────┐
│                       类加载流程                                  │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│   加载 ──▶ 验证 ──▶ 准备 ──▶ 解析 ──▶ 初始化 ──▶ 使用 ──▶ 卸载 │
│                                                                  │
│   ┌─────────────────────────────────────────────────────────┐   │
│   │                     验证阶段                            │   │
│   │  文件格式验证 → 元数据验证 → 字节码验证 → 符号引用验证   │   │
│   └─────────────────────────────────────────────────────────┘   │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

加载阶段

阶段说明
加载通过类全限定名获取二进制字节流
链接验证+准备+解析
初始化执行 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⭐⭐⭐⭐⭐⭐

⚠️ 易错点提醒

  1. 类的 ClassLoader 决定类的唯一性
  2. 双亲委派保证了核心类的安全
  3. Thread.currentThread().getContextClassLoader() 可以获取当前线程的 ClassLoader
  4. 类初始化顺序:父类 static -> 子类 static -> 父类构造 -> 子类构造
  5. 类卸载条件非常苛刻,实践中几乎不会发生