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


类加载器的层级结构

Java 类加载器分为三层(实际为四层,包括自定义加载器):

  1. 启动类加载器(Bootstrap ClassLoader)
    • 由 C++ 实现,是 JVM 的一部分。
    • 负责加载 JAVA_HOME/lib 下的核心类库(如 rt.jar)。
    • 唯一没有父加载器的加载器(顶级存在)。
  2. 扩展类加载器(Extension ClassLoader)
    • Java 实现,继承自 ClassLoader
    • 负责加载 JAVA_HOME/lib/ext 目录或 java.ext.dirs 指定路径的类。
    • 父加载器是 Bootstrap ClassLoader
  3. 应用程序类加载器(Application ClassLoader)
    • 也称为系统类加载器(AppClassLoader)。
    • 负责加载用户类路径(ClassPath)下的类(即项目的 classjar 包)。
    • 父加载器是 Extension ClassLoader
  4. 自定义类加载器
    • 用户可继承 ClassLoader 实现自己的加载器。
    • 父加载器默认为 Application ClassLoader

双亲委派流程

  1. 委派父加载器
    • AppClassLoader 收到请求时,先委派给父加载器 ExtClassLoader
    • ExtClassLoader 继续委派给父加载器 Bootstrap ClassLoader
  2. 父加载器尝试加载
    • Bootstrap ClassLoader 检查核心库(如 rt.jar)是否有该类。
      → 若有则加载成功;若无,则返回失败。
    • ExtClassLoader 检查 ext 目录是否有该类。
      → 若有则加载;若无,返回失败。
  3. 子加载器兜底加载
    • 若所有父加载器均失败,AppClassLoader 才尝试从 ClassPath 加载。
    • 若仍失败,抛出 ClassNotFoundException

为什么这样设计?

1. 避免重复加载,确保类唯一性

  • 父加载器加载过的类,子加载器不会再次加载。
  • 例如核心类 java.lang.ObjectBootstrap 加载,无论哪个加载器请求该类,最终都返回同一个 Class 对象,避免多个相同类在 JVM 中冲突

2. 保护核心类库安全

  • 防止用户自定义类冒充核心类(如恶意定义 java.lang.String)。
  • 由于双亲委派机制,核心类优先由 Bootstrap 加载,用户自定义的同名类不会被加载,保障 JVM 安全稳定

3. 职责分离,结构清晰

  • 每层加载器专注特定路径:
    • Bootstrap → 核心库
    • Ext → 扩展库
    • App → 用户代码
  • 避免类加载混乱,例如用户不会意外覆盖扩展库的类。

4. 灵活性与扩展性

  • 自定义类加载器可通过重写 findClass() 实现特殊加载逻辑(如热部署),同时默认仍遵循双亲委派保证安全。

打破双亲委派的场景

某些场景需打破双亲委派:

  1. SPI(Service Provider Interface)
    • 核心接口(如 JDBC 的 java.sql.Driver)由 Bootstrap 加载,但实现类(如 MySQL Driver)需由应用加载器加载。
    • 解决方案:使用 线程上下文类加载器(Thread Context ClassLoader) 逆向委派。
  2. 热部署/热更新
    • 如 Tomcat 为每个 Web 应用提供独立类加载器,优先加载自身目录的类(打破父优先)。
  3. OSGi 模块化
    • 实现更灵活的类加载策略。

总结

机制 核心思想 设计目的
双亲委派 子加载器委派父加载器优先加载类 1. 避免重复加载,保证类唯一性
2. 保护核心类安全
3. 职责分离,结构清晰
打破场景 逆向委派或子优先加载 解决 SPI、热部署等特殊需求

双亲委派是 Java 类加载体系的基石,平衡了安全性灵活性,而对其的打破则体现了 Java 生态的演进与适应能力。