双亲委派机制(Parent Delegation Model)是Java类加载器(ClassLoader)加载类时的一种策略。其核心思想是:当一个类加载器收到类加载请求时,首先将请求委派给父类加载器处理,只有在父类加载器无法完成加载时,子类加载器才会尝试加载。
一、什么是类加载器?
Java 的类是从 .class 文件加载到 JVM 内存中才能运行的,这个加载过程由类加载器(ClassLoader)负责。
JVM 默认有三层类加载器(其实是四层,包括最顶层的 Bootstrap):

-
Bootstrap ClassLoader(启动类加载器)
- 用 C++ 写的(不是 Java 类),是 JVM 的一部分。
- 负责加载
<JAVA_HOME>/lib下的核心类库,比如java.lang.String、java.util.ArrayList等。 - 我们用
String.class.getClassLoader()会得到 null,就是因为它不是 Java 对象。
-
Extension ClassLoader(扩展类加载器)
- 负责加载
<JAVA_HOME>/lib/ext下的扩展 jar 包。
- 负责加载
-
Application ClassLoader(应用类加载器,也叫系统类加载器)
- 负责加载我们自己写的代码(classpath 下的类)。
- 通常是我们程序的默认类加载器。
-
自定义类加载器
- 我们继承
ClassLoader自己写的,用于特殊需求(如热部署、加密加载等)。
- 我们继承
二、双亲委派模型到底是什么?
双亲委派模型是 JVM 类加载的默认行为,核心规则就三步:
- 当一个类加载器收到类加载请求时,不会自己先去加载。
- 而是把这个请求向上委托给父类加载器去完成。
- 如果父类加载器加载不了(找不到类),才由当前类加载器自己尝试加载。
形象比喻:
就像儿子想买玩具,先问爸爸有没有,爸爸没有再问爷爷,爷爷也没有才自己去买。
代码层面,在 ClassLoader.loadClass() 方法中大致是这样的逻辑:
java
protected Class<?> loadClass(String name, boolean resolve) {
// 先检查是否已经加载过
Class<?> c = findLoadedClass(name);
if (c == null) {
// 委托给父类加载器
if (parent != null) {
c = parent.loadClass(name, false);
} else {
// 父是 null,委托给 Bootstrap
c = findBootstrapClassOrNull(name);
}
}
if (c == null) {
// 父类都加载不了,自己尝试
c = findClass(name);
}
return c;
}
三、双亲委派模型的好处(为什么要有这条家规?)
-
安全性最高
防止用户自己写的类覆盖核心类。
比如你写一个
java.lang.String,想搞破坏?不可能!因为加载java.lang.String时,会优先委托给 Bootstrap ClassLoader,它直接从 rt.jar 加载原版,你的恶意版本根本没机会被加载。 -
避免类重复加载
同一个类不会被加载多次,节省内存,也避免了类型转换问题(同一个类在不同加载器中是不同类型)。
-
提供稳定的命名空间
核心类永远只有一个版本,所有代码都引用同一个
java.lang.Object,不会出现混乱。
四、一个生动例子
假设我们要加载 java.lang.String:
- 我们的代码 → Application ClassLoader 收到请求
- → 委托给 Extension ClassLoader
- → 再委托给 Bootstrap ClassLoader
- → Bootstrap 在 rt.jar 找到 String.class → 加载成功 → 返回
- → 层层返回,最终我们拿到核心的 String 类
如果我们要加载自己写的 com.example.MyClass:
- Application ClassLoader 收到请求
- → 委托 Extension(找不到)
- → 委托 Bootstrap(找不到)
- → 自己去 classpath 下找 → 找到并加载
完美分工!
五、双亲委派可以被"破坏"吗?
可以!只要重写 loadClass() 方法,不按双亲委派逻辑走就行。
常见破坏场景(大厂框架都在用):
-
Tomcat Web 容器
每个 Web 应用有自己的 ClassLoader,先尝试自己加载 webapp 中的类(实现应用隔离),加载不到再委托父类。这样不同应用可以有不同版本的 jar 包(如不同版本的 Spring)。
-
SPI 机制(Service Provider Interface)
如 JDBC:核心 API 在 rt.jar(Bootstrap 加载),但具体驱动(如 MySQL Driver)在我们 classpath。
Bootstrap 无法直接加载我们的驱动,所以用线程上下文类加载器(Thread Context ClassLoader,通常是 Application ClassLoader)来加载。
-
OSGi、Dubbo、Spring Boot 等模块化/热部署框架
需要更灵活的类加载策略,支持模块隔离和热替换。
六、自定义类加载器示例(破坏双亲委派)
java
public class MyClassLoader extends ClassLoader {
@Override
protected Class<?> loadClass(String name, boolean resolve) {
// 先自己尝试加载(破坏双亲委派)
Class<?> c = findLoadedClass(name);
if (c == null) {
try {
c = findClass(name); // 自己加载
} catch (ClassNotFoundException e) {
// 自己加载不了,再委托父类
c = super.loadClass(name, resolve);
}
}
return c;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
// 从自定义路径读取字节码并定义类
byte[] classData = loadClassData(name);
return defineClass(name, classData, 0, classData.length);
}
}
七、总结:双亲委派模型的核心价值
| 方面 | 双亲委派遵守时 | 破坏双亲委派时 |
|---|---|---|
| 安全性 | 高(核心类不可被覆盖) | 降低(需自己保证安全) |
| 类唯一性 | 保证(同一个类只加载一次) | 可能重复或冲突 |
| 适用场景 | 普通应用 | 插件化、热部署、容器、SPI |
| 复杂度 | 低 | 高 |
双亲委派模型是 Java 生态稳定运行的基石之一。99% 的情况下我们不需要关心它,但当你做框架、中间件、热部署时,就必须理解甚至"破坏"它。
掌握了双亲委派,你就真正理解了 Java 类加载的精髓,也能更好地阅读 Spring、Tomcat 等大厂框架源码。