JVM 双亲委派模型

双亲委派机制(Parent Delegation Model)是Java类加载器(ClassLoader)加载类时的一种策略。其核心思想是:当一个类加载器收到类加载请求时,首先将请求委派给父类加载器处理,只有在父类加载器无法完成加载时,子类加载器才会尝试加载。

一、什么是类加载器?

Java 的类是从 .class 文件加载到 JVM 内存中才能运行的,这个加载过程由类加载器(ClassLoader)负责。

JVM 默认有三层类加载器(其实是四层,包括最顶层的 Bootstrap):

  1. Bootstrap ClassLoader(启动类加载器)

    • 用 C++ 写的(不是 Java 类),是 JVM 的一部分。
    • 负责加载 <JAVA_HOME>/lib 下的核心类库,比如 java.lang.Stringjava.util.ArrayList 等。
    • 我们用 String.class.getClassLoader() 会得到 null,就是因为它不是 Java 对象。
  2. Extension ClassLoader(扩展类加载器)

    • 负责加载 <JAVA_HOME>/lib/ext 下的扩展 jar 包。
  3. Application ClassLoader(应用类加载器,也叫系统类加载器)

    • 负责加载我们自己写的代码(classpath 下的类)。
    • 通常是我们程序的默认类加载器。
  4. 自定义类加载器

    • 我们继承 ClassLoader 自己写的,用于特殊需求(如热部署、加密加载等)。

二、双亲委派模型到底是什么?

双亲委派模型是 JVM 类加载的默认行为,核心规则就三步:

  1. 当一个类加载器收到类加载请求时,不会自己先去加载
  2. 而是把这个请求向上委托给父类加载器去完成。
  3. 如果父类加载器加载不了(找不到类),才由当前类加载器自己尝试加载。

形象比喻:

就像儿子想买玩具,先问爸爸有没有,爸爸没有再问爷爷,爷爷也没有才自己去买。

代码层面,在 ClassLoader.loadClass() 方法中大致是这样的逻辑:

java 复制代码
protected Class<?> loadClass(String name, boolean resolve) {
    // 先检查是否已经加载过
    Class<?> c = findLoadedClass(name);
    if (c == null) {
        // 委托给父类加载器
        if (parent != null) {
            c = parent.loadClass(name, false);
        } else {
            // 父是 null,委托给 Bootstrap
            c = findBootstrapClassOrNull(name);
        }
    }
    if (c == null) {
        // 父类都加载不了,自己尝试
        c = findClass(name);
    }
    return c;
}

三、双亲委派模型的好处(为什么要有这条家规?)

  1. 安全性最高

    防止用户自己写的类覆盖核心类。

    比如你写一个 java.lang.String,想搞破坏?不可能!因为加载 java.lang.String 时,会优先委托给 Bootstrap ClassLoader,它直接从 rt.jar 加载原版,你的恶意版本根本没机会被加载。

  2. 避免类重复加载

    同一个类不会被加载多次,节省内存,也避免了类型转换问题(同一个类在不同加载器中是不同类型)。

  3. 提供稳定的命名空间

    核心类永远只有一个版本,所有代码都引用同一个 java.lang.Object,不会出现混乱。

四、一个生动例子

假设我们要加载 java.lang.String

  • 我们的代码 → Application ClassLoader 收到请求
  • → 委托给 Extension ClassLoader
  • → 再委托给 Bootstrap ClassLoader
  • → Bootstrap 在 rt.jar 找到 String.class → 加载成功 → 返回
  • → 层层返回,最终我们拿到核心的 String 类

如果我们要加载自己写的 com.example.MyClass

  • Application ClassLoader 收到请求
  • → 委托 Extension(找不到)
  • → 委托 Bootstrap(找不到)
  • → 自己去 classpath 下找 → 找到并加载

完美分工!

五、双亲委派可以被"破坏"吗?

可以!只要重写 loadClass() 方法,不按双亲委派逻辑走就行。

常见破坏场景(大厂框架都在用):

  1. Tomcat Web 容器

    每个 Web 应用有自己的 ClassLoader,先尝试自己加载 webapp 中的类(实现应用隔离),加载不到再委托父类。这样不同应用可以有不同版本的 jar 包(如不同版本的 Spring)。

  2. SPI 机制(Service Provider Interface)

    如 JDBC:核心 API 在 rt.jar(Bootstrap 加载),但具体驱动(如 MySQL Driver)在我们 classpath。

    Bootstrap 无法直接加载我们的驱动,所以用线程上下文类加载器(Thread Context ClassLoader,通常是 Application ClassLoader)来加载。

  3. OSGi、Dubbo、Spring Boot 等模块化/热部署框架

    需要更灵活的类加载策略,支持模块隔离和热替换。

六、自定义类加载器示例(破坏双亲委派)

java 复制代码
public class MyClassLoader extends ClassLoader {
    @Override
    protected Class<?> loadClass(String name, boolean resolve) {
        // 先自己尝试加载(破坏双亲委派)
        Class<?> c = findLoadedClass(name);
        if (c == null) {
            try {
                c = findClass(name);  // 自己加载
            } catch (ClassNotFoundException e) {
                // 自己加载不了,再委托父类
                c = super.loadClass(name, resolve);
            }
        }
        return c;
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        // 从自定义路径读取字节码并定义类
        byte[] classData = loadClassData(name);
        return defineClass(name, classData, 0, classData.length);
    }
}

七、总结:双亲委派模型的核心价值

方面 双亲委派遵守时 破坏双亲委派时
安全性 高(核心类不可被覆盖) 降低(需自己保证安全)
类唯一性 保证(同一个类只加载一次) 可能重复或冲突
适用场景 普通应用 插件化、热部署、容器、SPI
复杂度

双亲委派模型是 Java 生态稳定运行的基石之一。99% 的情况下我们不需要关心它,但当你做框架、中间件、热部署时,就必须理解甚至"破坏"它。

掌握了双亲委派,你就真正理解了 Java 类加载的精髓,也能更好地阅读 Spring、Tomcat 等大厂框架源码。

相关推荐
代码or搬砖18 小时前
JVM 类加载机制
jvm
我尽力学18 小时前
JVM类加载子系统、类加载机制
jvm
小罗和阿泽19 小时前
java [多线程基础 二】
java·开发语言·jvm
小罗和阿泽19 小时前
java 【多线程基础 一】线程概念
java·开发语言·jvm
隐退山林19 小时前
JavaEE:多线程初阶(一)
java·开发语言·jvm
xie_pin_an19 小时前
C++ 类和对象全解析:从基础语法到高级特性
java·jvm·c++
是一个Bug20 小时前
Java后端开发面试题清单(50道)
java·开发语言·jvm
曹轲恒20 小时前
JVM——类加载机制
jvm
木风小助理20 小时前
Android 数据库实操指南:从 SQLite 到 Realm,不同场景精准匹配
jvm·数据库·oracle