Java双亲委派机制
Java 中的双亲委派机制(Parent Delegation Model) 是类加载器(ClassLoader)加载类的核心机制,其核心思想是:当一个类加载器收到加载类的请求时,它不会立即尝试加载,而是先将请求委派给父类加载器处理。只有在父类加载器无法完成加载时,子加载器才会尝试加载。
类加载器的层级结构
Java 类加载器分为三层(实际为四层,包括自定义加载器):
- 启动类加载器(Bootstrap ClassLoader)
- 由 C++ 实现,是 JVM 的一部分。
- 负责加载
JAVA_HOME/lib
下的核心类库(如rt.jar
)。 - 唯一没有父加载器的加载器(顶级存在)。
- 扩展类加载器(Extension ClassLoader)
- Java 实现,继承自
ClassLoader
。 - 负责加载
JAVA_HOME/lib/ext
目录或java.ext.dirs
指定路径的类。 - 父加载器是 Bootstrap ClassLoader。
- Java 实现,继承自
- 应用程序类加载器(Application ClassLoader)
- 也称为系统类加载器(
AppClassLoader
)。 - 负责加载用户类路径(ClassPath)下的类(即项目的
class
或jar
包)。 - 父加载器是 Extension ClassLoader。
- 也称为系统类加载器(
- 自定义类加载器
- 用户可继承
ClassLoader
实现自己的加载器。 - 父加载器默认为 Application ClassLoader。
- 用户可继承
双亲委派流程
- 委派父加载器
- 当
AppClassLoader
收到请求时,先委派给父加载器ExtClassLoader
。 ExtClassLoader
继续委派给父加载器Bootstrap ClassLoader
。
- 当
- 父加载器尝试加载
Bootstrap ClassLoader
检查核心库(如rt.jar
)是否有该类。
→ 若有则加载成功;若无,则返回失败。ExtClassLoader
检查ext
目录是否有该类。
→ 若有则加载;若无,返回失败。
- 子加载器兜底加载
- 若所有父加载器均失败,
AppClassLoader
才尝试从 ClassPath 加载。 - 若仍失败,抛出
ClassNotFoundException
。
- 若所有父加载器均失败,
为什么这样设计?
1. 避免重复加载,确保类唯一性
- 父加载器加载过的类,子加载器不会再次加载。
- 例如核心类
java.lang.Object
由Bootstrap
加载,无论哪个加载器请求该类,最终都返回同一个Class
对象,避免多个相同类在 JVM 中冲突。
2. 保护核心类库安全
- 防止用户自定义类冒充核心类(如恶意定义
java.lang.String
)。 - 由于双亲委派机制,核心类优先由
Bootstrap
加载,用户自定义的同名类不会被加载,保障 JVM 安全稳定。
3. 职责分离,结构清晰
- 每层加载器专注特定路径:
Bootstrap
→ 核心库Ext
→ 扩展库App
→ 用户代码
- 避免类加载混乱,例如用户不会意外覆盖扩展库的类。
4. 灵活性与扩展性
- 自定义类加载器可通过重写
findClass()
实现特殊加载逻辑(如热部署),同时默认仍遵循双亲委派保证安全。
打破双亲委派的场景
某些场景需打破双亲委派:
- SPI(Service Provider Interface)
- 核心接口(如 JDBC 的
java.sql.Driver
)由Bootstrap
加载,但实现类(如 MySQL Driver)需由应用加载器加载。 - 解决方案:使用 线程上下文类加载器(Thread Context ClassLoader) 逆向委派。
- 核心接口(如 JDBC 的
- 热部署/热更新
- 如 Tomcat 为每个 Web 应用提供独立类加载器,优先加载自身目录的类(打破父优先)。
- OSGi 模块化
- 实现更灵活的类加载策略。
总结
机制 | 核心思想 | 设计目的 |
---|---|---|
双亲委派 | 子加载器委派父加载器优先加载类 | 1. 避免重复加载,保证类唯一性 2. 保护核心类安全 3. 职责分离,结构清晰 |
打破场景 | 逆向委派或子优先加载 | 解决 SPI、热部署等特殊需求 |
双亲委派是 Java 类加载体系的基石,平衡了安全性与灵活性,而对其的打破则体现了 Java 生态的演进与适应能力。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 技术之路!
评论