下面用最直白、面试可直接背的方式讲清楚 双亲委派机制(Parent Delegation Model)。
一、一句话定义
类加载器收到加载请求 → 先交给父加载器 → 父加载不了 → 自己再加载。
目的:保证核心类安全、全局唯一、防止篡改。
"双亲"不是两个父类,是层级链上的父加载器(翻译问题)。
二、三层加载器(JDK8)
从上到下:
-
Bootstrap ClassLoader(启动类加载器)
- C++写的,JVM内置
- 加载:
rt.jar核心类(java.lang.String、Object...) - 路径:
$JAVA_HOME/lib
-
Extension ClassLoader(扩展类加载器)
- Java写的
- 加载:
lib/ext或java.ext.dirs的扩展类
-
Application ClassLoader(应用/系统类加载器)
- Java写的
- 加载:项目
classpath下的类、第三方jar - 日常代码几乎都是它加载
父子关系是 组合(parent 字段),不是 Java 继承。
三、执行流程(必背)
以加载 com.example.MyClass 为例:
- AppClassLoader 收到请求
- 检查:是否已加载过? → 是,直接返回
- 没加载 → 委派给父:ExtClassLoader
- ExtClassLoader 同样:有缓存返回,没缓存 → 委派给 Bootstrap
- Bootstrap:
- 在
rt.jar找 → 找不到(因为是你自己的类) - 返回"加载失败"
- 在
- 回到 ExtClassLoader:自己路径也找不到 → 失败
- 回到 AppClassLoader:自己调用 findClass 加载 → 成功
总结:自下而上委派,自上而下加载;父优先,子兜底。
四、核心作用(面试高频)
-
安全:防止核心类被篡改
- 你自己写一个
java.lang.String,不会被加载 - 因为 Bootstrap 优先加载 rt.jar 里的正版 String
- 你自己写一个
-
唯一:一个类在 JVM 中只一份
- 全链路委派,保证不会重复加载
-
隔离:不同加载器的类互相隔离
- 例如:AppClassLoader 加载的 A 类 ≠ 自定义加载器加载的 A 类
五、什么时候打破双亲委派?
默认是双亲委派,但有些场景必须打破:
-
Tomcat:
- 每个 Web 应用有自己的类加载器
- 先加载自己 war 包里的类,再委派父加载器
- 目的:Web 应用之间类隔离、热部署
-
JDBC 驱动加载:
- Bootstrap 加载
java.sql.Driver(接口) - 实现类在应用 jar 里,需要反向委派
- Bootstrap 加载
-
热部署、OSGi、模块化框架
打破方式:重写 ClassLoader 的 loadClass 方法,不先委派父加载器。
六、源码核心(简化版)
java
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
// 1. 检查是否已加载
Class<?> c = findLoadedClass(name);
if (c == null) {
try {
if (parent != null) {
// 2. 委派父加载器
c = parent.loadClass(name, false);
} else {
// 3. 父为null → 调用Bootstrap
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// 父加载失败
}
if (c == null) {
// 4. 自己加载
c = findClass(name);
}
}
if (resolve) resolveClass(c);
return c;
}
关键:loadClass 默认先委派;findClass 由子类重写实现自己的加载逻辑。
七、面试极简背诵版
- 定义:加载类时先委派父加载器,父不能加载才自己加载。
- 三层:Bootstrap → Ext → App。
- 流程:自下而上委派,自上而下加载。
- 作用:安全防篡改、全局唯一、类隔离。
- 打破:重写 loadClass,如 Tomcat、JDBC。