双亲委派机制:一句话说清楚它是啥
一句话总结 :
双亲委派就是------类加载时,先让父类加载器去加载,父类加载不了,才由自己加载 。
目的是保证核心类由高层加载器统一加载,防止被替换,确保安全和唯一。
一、什么是"双亲委派"?
"双亲"其实是翻译问题,它不是指"父母双方",而是"父辈"的意思。更准确的说法是"父类委派"。
它的规则只有一条:
当一个类加载器收到加载类的请求时,它不急着自己加载,而是先把请求交给父类加载器去处理。只有父类加载器明确表示"我加载不了",自己才动手。
这个"父",不是 Java 中的继承关系,而是类加载器之间的委托关系。
二、类加载器有哪些?
Java 中的类加载器分为四层,从上到下是:
类加载器 | 实现方式 | 负责加载 |
---|---|---|
Bootstrap ClassLoader | JVM 用 C++ 实现 | rt.jar 等核心类库(如 java.lang.* ) |
Extension ClassLoader | Java 实现 | jre/lib/ext 目录下的扩展类 |
Application ClassLoader | Java 实现 | classpath 下的用户类(你的代码) |
自定义 ClassLoader | 继承 ClassLoader |
特殊需求,如网络加载、热部署 |
它们之间形成一条链:
自定义 → Application → Extension → Bootstrap
加载请求从下往上层层委派,一旦某一层能加载,就直接返回,不再往下。
三、为什么要有双亲委派?
1. 防止核心类被篡改
试想:如果你写了一个 java.lang.String
,放在项目里。
如果没有双亲委派,Application ClassLoader 可能会加载你写的这个"假" String
,那整个系统就乱了。
但因为有双亲委派,加载请求会先被委派到 Bootstrap ClassLoader,它从 rt.jar
中加载了真正的 String
,你的类根本不会被加载。
✅ 所以,你永远无法替换核心类。
2. 避免类重复加载
同一个类,比如 java.util.ArrayList
,如果被多个类加载器各自加载,就会出现多个 Class
对象。
这会导致:
instanceof
判断失败;- 类型转换异常;
- 静态变量不共享。
双亲委派确保这类核心类只被最顶层的加载器加载一次,保证唯一性。
3. 安全
核心类库由最可信的 Bootstrap ClassLoader 加载,它不走 Java 层代码,不受用户控制。
这种自顶向下的信任链,让 JVM 能抵御恶意代码的攻击。
四、双亲委派是怎么实现的?
核心就在 ClassLoader.loadClass()
方法里。
JDK 8 中的关键逻辑如下:
java
protected Class<?> loadClass(String name, boolean resolve) {
// 1. 先查自己是否已加载
Class<?> c = findLoadedClass(name);
if (c == null) {
try {
// 2. 有父类,先让父类加载
if (parent != null) {
c = parent.loadClass(name, false);
} else {
// 父为 null,表示使用 Bootstrap
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// 父类加载失败,继续
}
// 3. 父类没加载成功,自己加载
if (c == null) {
c = findClass(name);
}
}
return c;
}
这个方法定义了标准流程 :
查缓存 → 委派父类 → 自己加载。
除非你重写它,否则所有类加载器都必须遵守。
五、双亲委派能打破吗?
能。通过重写 loadClass
方法,可以改成"先自己加载,再委派父类",这就是打破双亲委派。
常见场景:
- Tomcat:每个 Web 应用用独立类加载器,避免应用之间类冲突;
- OSGi:模块化框架,支持不同模块用不同版本的同一个库;
- 热部署:重新加载类而不重启 JVM。
代价:
- 类唯一性没了;
- 需要自己处理类隔离、冲突、卸载等问题;
- 复杂度高,一般只在框架层面使用。