JVM 类加载:双亲委派机制

Java教程 2025-09-17

双亲委派机制:一句话说清楚它是啥


一、什么是“双亲委派”?

“双亲”其实是翻译问题,它不是指“父母双方”,而是“父辈”的意思。更准确的说法是“父类委派”。

它的规则只有一条:

这个“父”,不是 Java 中的继承关系,而是类加载器之间的委托关系


二、类加载器有哪些?

Java 中的类加载器分为四层,从上到下是:

类加载器实现方式负责加载
Bootstrap ClassLoaderJVM 用 C++ 实现rt.jar 等核心类库(如 java.lang.*
Extension ClassLoaderJava 实现jre/lib/ext 目录下的扩展类
Application ClassLoaderJava 实现classpath 下的用户类(你的代码)
自定义 ClassLoader继承 ClassLoader特殊需求,如网络加载、热部署

它们之间形成一条链:

自定义 → Application → Extension → Bootstrap

加载请求从下往上层层委派,一旦某一层能加载,就直接返回,不再往下。


三、为什么要有双亲委派?

1. 防止核心类被篡改

试想:如果你写了一个 java.lang.String,放在项目里。

如果没有双亲委派,Application ClassLoader 可能会加载你写的这个“假” String,那整个系统就乱了。

但因为有双亲委派,加载请求会先被委派到 Bootstrap ClassLoader,它从 rt.jar 中加载了真正的 String,你的类根本不会被加载。

所以,你永远无法替换核心类

2. 避免类重复加载

同一个类,比如 java.util.ArrayList,如果被多个类加载器各自加载,就会出现多个 Class 对象。

这会导致:

  • instanceof 判断失败;
  • 类型转换异常;
  • 静态变量不共享。

双亲委派确保这类核心类只被最顶层的加载器加载一次,保证唯一性。

3. 安全

核心类库由最可信的 Bootstrap ClassLoader 加载,它不走 Java 层代码,不受用户控制。

这种自顶向下的信任链,让 JVM 能抵御恶意代码的攻击。


四、双亲委派是怎么实现的?

核心就在 ClassLoader.loadClass() 方法里。

JDK 8 中的关键逻辑如下:

protected Class loadClass(String name, boolean resolve) {
    // 1. 先查自己是否已加载
    Class c = findLoadedClass(name);
    if (c == null) {
        try {
            // 2. 有父类,先让父类加载
            if (parent != null) {
                c = parent.loadClass(name, false);
            } else {
                // 父为 null,表示使用 Bootstrap
                c = findBootstrapClassOrNull(name);
            }
        } catch (ClassNotFoundException e) {
            // 父类加载失败,继续
        }
        // 3. 父类没加载成功,自己加载
        if (c == null) {
            c = findClass(name);
        }
    }
    return c;
}

这个方法定义了标准流程
查缓存 → 委派父类 → 自己加载。

除非你重写它,否则所有类加载器都必须遵守。


五、双亲委派能打破吗?

能。通过重写 loadClass 方法,可以改成“先自己加载,再委派父类”,这就是打破双亲委派

常见场景:

  • Tomcat:每个 Web 应用用独立类加载器,避免应用之间类冲突;
  • OSGi:模块化框架,支持不同模块用不同版本的同一个库;
  • 热部署:重新加载类而不重启 JVM。

代价:

  • 类唯一性没了;
  • 需要自己处理类隔离、冲突、卸载等问题;
  • 复杂度高,一般只在框架层面使用。