双亲委派模型
双亲委派模型(Parent Delegation Model)是 Java 类加载器(ClassLoader)的核心设计模式,用于保证类加载的安全性和唯一性,避免类重复加载和核心类被篡改。
一、核心原理
当一个类加载器收到类加载请求时,它不会立即自己加载,而是先委派给父类加载器,逐层向上传递请求;只有当父类加载器无法加载该类(在其搜索范围内找不到)时,子类加载器才会尝试自己加载。
简单来说:先找爹,爹不行,自己上。
二、类加载器层级结构
Java 中的类加载器按层级从上到下分为:
-
启动类加载器(Bootstrap ClassLoader)
- 最顶层,由 C++ 实现(非 Java 类),负责加载
$JAVA_HOME/jre/lib/rt.jar(核心类库,如java.lang.String)。 - 无父类加载器,也无法通过 Java 代码直接引用。
- 最顶层,由 C++ 实现(非 Java 类),负责加载
-
扩展类加载器(Extension ClassLoader)
- 父类是启动类加载器,负责加载
$JAVA_HOME/jre/lib/ext/*.jar(扩展类库)。 - 由
sun.misc.Launcher$ExtClassLoader实现。
- 父类是启动类加载器,负责加载
-
应用程序类加载器(Application ClassLoader)
- 父类是扩展类加载器,负责加载用户类路径(
ClassPath)下的类(项目代码、第三方依赖)。 - 由
sun.misc.Launcher$AppClassLoader实现,也是默认的系统类加载器。
- 父类是扩展类加载器,负责加载用户类路径(
-
自定义类加载器(Custom ClassLoader)
- 开发者继承
java.lang.ClassLoader实现,用于加载自定义路径的类(如 Tomcat 的 WebAppClassLoader)。
- 开发者继承
三、工作流程
以加载com.example.Test类为例:
- 应用程序类加载器收到请求,先委派给父类扩展类加载器。
- 扩展类加载器再委派给父类启动类加载器。
- 启动类加载器检查核心类库,发现没有
com.example.Test,返回 “无法加载”。 - 扩展类加载器检查扩展类库,也没有,返回 “无法加载”。
- 应用程序类加载器在
ClassPath下查找并加载com.example.Test类。
四、优势
- 安全性:核心类(如
java.lang.String)只能由启动类加载器加载,避免用户自定义同名类篡改核心 API(防止 “String 类被替换” 等安全问题)。 - 唯一性:同一个类只会被加载一次(由最顶层的类加载器加载),保证类的全局唯一性。
- 层级隔离:不同层级的类加载器负责不同范围的类,避免类冲突。
五、打破双亲委派模型的场景
并非所有场景都严格遵循双亲委派,以下情况会 “反向委派” 或直接加载:
- SPI 机制(Service Provider Interface) 例如 JDBC 驱动(
java.sql.Driver):核心类Driver由启动类加载器加载,但具体驱动实现(如 MySQL 驱动)在ClassPath下,需由应用程序类加载器加载 → 启动类加载器会委派子类加载器加载(线程上下文类加载器实现)。 - Tomcat 等容器Tomcat 为每个 Web 应用创建独立的类加载器(WebAppClassLoader),优先加载应用内的类(而非委派父类),避免不同应用的类冲突。
- 热部署 / 动态加载自定义类加载器需实时加载新类,可能绕过双亲委派直接加载。