双亲委派(Parent Delegation)是 Java 类加载机制中的一种核心设计原则,用于确保类的唯一性、安全性和层次化加载。它的核心思想是:当一个类加载器需要加载某个类时,不会立即自己尝试加载,而是先将请求委派给父类加载器处理。这种逐级向上委派的机制形成了类加载的层次结构。
双亲委派的工作流程
-
自底向上委派
当子类加载器收到加载类的请求时:-
先检查该类是否已被加载过。
-
若未加载,优先委派给父类加载器处理(而非自己直接加载)。
-
父类加载器会重复此过程,直到请求传递到顶层的 启动类加载器(Bootstrap ClassLoader)。
-
-
自顶向下尝试加载
-
如果父类加载器能完成加载,直接返回该类。
-
如果父类加载器无法加载(例如父类加载器的搜索范围内没有该类),子类加载器才会尝试自己加载。
-
类加载器的层次结构
Java 默认类加载器按层级从高到低分为:
-
启动类加载器(Bootstrap ClassLoader)
-
由 C++ 实现,负责加载
JRE/lib
下的核心类库(如rt.jar
)。
-
-
扩展类加载器(Extension ClassLoader)
-
由 Java 实现,负责加载
JRE/lib/ext
目录下的类。
-
-
应用程序类加载器(Application ClassLoader)
-
加载用户类路径(
classpath
)下的类。
-
-
自定义类加载器(Custom ClassLoader)
-
用户可自定义类加载器,继承
ClassLoader
类。
-
双亲委派的优点
-
避免重复加载
父类加载器加载过的类,子类加载器不会重复加载,确保类的唯一性。-
例如:
java.lang.Object
始终由启动类加载器加载,全局唯一。
-
-
安全性
防止用户自定义的类冒充核心类(如伪造java.lang.String
),因为父类加载器会优先加载核心类库。 -
职责分离
不同层级的类加载器负责不同范围的类加载,形成清晰的边界。
双亲委派的例外情况
某些场景会打破双亲委派机制:
-
SPI(Service Provider Interface)
-
核心类(如 JDBC)需调用第三方实现(如 MySQL 驱动)。
-
通过 线程上下文类加载器(Thread Context ClassLoader) 逆向委派子类加载器加载。
-
-
热部署/热替换
-
如 Tomcat 为每个 Web 应用提供独立的类加载器,优先加载自身目录的类,未找到再委派父类加载器。
-
-
模块化系统(如 OSGi)
-
允许类加载器网状委派,实现动态模块化加载。
-
代码示例
类加载器的 loadClass
方法默认实现双亲委派逻辑:
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
// 1. 检查是否已加载
Class<?> c = findLoadedClass(name);
if (c == null) {
try {
// 2. 委派父类加载器
if (parent != null) {
c = parent.loadClass(name, false);
} else {
// 父类加载器为 Bootstrap ClassLoader
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// 父类加载失败,不抛异常
}
if (c == null) {
// 3. 父类无法加载时,自己尝试加载
c = findClass(name);
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
总结
双亲委派是 Java 类加载的核心机制,通过逐级向上委派、向下尝试加载,保障了类的一致性和安全性。尽管某些场景需要打破这一机制(如 SPI、热部署),但其设计思想仍是理解 Java 类加载体系的基础。