java双亲委派机制
双亲委派机制 (Parent Delegation Model) 是 Java 类加载器 (ClassLoader) 用来加载类和保证类安全一致性的一种核心工作模型和规则。它是 Java 安全模型和确保核心类库不被篡改的基石。
核心思想:
“向上委托,向下加载”。简单来说:当一个类加载器需要加载某个类时,它不会立即自己尝试去加载,而是先向上委托给它的父类加载器去尝试加载。这个委托过程会逐层向上进行(从 AppClassLoader-> ExtClassLoader-> Bootstrap ClassLoader)。只有父类加载器反馈“我加载不了”(在它们的搜索范围内找不到所需的类)时,子加载器才会自己尝试去加载。
工作原理(步骤):
-
接收请求: 一个类加载器(我们称为
ChildClassLoader,例如AppClassLoader)接收到加载类com.example.MyClass的请求。 -
检查缓存:
ChildClassLoader首先检查自己是否已经加载过这个类。如果已经加载过,则直接返回已加载的Class对象,加载过程结束。 -
委派父加载器: 如果自己没加载过,
ChildClassLoader不会立即自己去加载,而是调用其 parent.loadClass() 方法(如果父类加载器存在),将加载请求委派给它的父类加载器。 -
递归委派: 父类加载器(例如
ExtClassLoader)收到委派后,重复步骤1-3:-
检查自己是否已加载。
-
如果没加载过,它也会将请求委派给 它的父加载器(
Bootstrap ClassLoader)。
-
-
父加载器尝试加载: 最终,请求会到达最顶层的
Bootstrap ClassLoader。-
Bootstrap首先检查自己是否加载过 (rt.jar等核心库)。 -
如果找到了,则返回
Class对象,加载结束。 -
如果在自己的搜索范围内(
JAVA_HOME/lib等)没找到,则返回“加载失败”。
-
-
父加载器加载失败后:
-
请求沿着委派链逐层向下返回“加载失败”。
-
直到到达最初发起请求的
ChildClassLoader。
-
-
子加载器自行加载: 只有接收到 所有父加载器都“加载失败”的信号后,
ChildClassLoader才开始在 自己负责的路径上(例如:应用classpath)查找并加载com.example.MyClass。-
如果在自己负责的范围内找到了
.class文件,就加载、链接、初始化。 -
如果在自己负责的范围内也没找到,则抛出
ClassNotFoundException。
-
类加载器层次(经典模型 - JDK 8及之前):
Bootstrap ClassLoader (根加载器)
^
|
Extension ClassLoader (ExtClassLoader)
^
|
System / Application ClassLoader (AppClassLoader)
^
|
(用户自定义加载器 1, 用户自定义加载器 2, ...)
-
Bootstrap ClassLoader: 顶层的父加载器(通常是
null表示)。用 C++ 实现,无 Java 父类。负责加载<JAVA_HOME>/lib下的核心库(如rt.jar,charsets.jar)。 -
Extension ClassLoader (ExtClassLoader): 继承自
java.net.URLClassLoader。父加载器是Bootstrap(可能显示为null)。负责加载<JAVA_HOME>/lib/ext目录下的 JAR 包,或java.ext.dirs系统变量指定目录下的类。 -
Application / System ClassLoader (AppClassLoader /
sun.misc.Launcher$AppClassLoader): 继承自java.net.URLClassLoader。开发者最常见到的加载器。 父加载器是ExtClassLoader。负责加载环境变量CLASSPATH、-classpath或-cp命令行参数所指定路径下的类库和第三方 Jar 包。ClassLoader.getSystemClassLoader()默认返回它。 -
自定义 ClassLoader: 开发者可以继承
ClassLoader类创建自己的加载器。默认情况下(不显式指定父加载器),它的父加载器就是AppClassLoader。
为什么需要双亲委派机制?核心目的和好处:
-
保证核心类库的安全性和一致性: 这是最重要的目的。
-
防止用户随意编写或替换基础核心类(如
java.lang.Object,java.lang.String,java.lang.Class)。想象一下,如果用户在自己的 classpath 下放了一个自定义的java.lang.Object类,如果没有双亲委派,AppClassLoader直接加载了这个自定义Object,JVM 运行的基础就崩溃了! -
双亲委派确保了这些核心类总是由最顶层的
Bootstrap ClassLoader加载。用户自定义的同名核心类不会被加载(因为父加载器优先,并且父加载器在核心目录下已经加载了标准类)。 -
保证了核心类的唯一性。无论是在哪个加载器的上下文中引用
java.lang.Object,它指向的都是同一个Bootstrap加载的类,避免了多个不同版本的核心类共存导致混乱。
-
-
避免重复加载: 当父加载器已经加载了一个类时,子加载器就没必要再去加载了,直接使用父加载器加载结果即可。这提高了效率。
-
提供一种类加载的优先级和范围控制: 类加载器层次结构定义了什么类应该由谁加载。核心类归
Bootstrap,扩展类归Ext,应用类归App。职责分明。
如何打破双亲委派?
虽然双亲委派是默认规则,但在特定场景下会被打破(需要开发者明确编写代码实现):
-
SPI 服务发现 (Service Provider Interface): 核心类库(如
java.sql.Driver)定义接口,由第三方厂商(如 MySQL JDBC Driver)实现。这些实现需要在AppClassLoader(或自定义加载器)中加载,但接口本身由Bootstrap加载。-
解决方法: 利用
线程上下文类加载器 (Thread Context ClassLoader, TCCL)。例如,java.sql.DriverManager(在Bootstrap中)会使用当前线程的 TCCL(通常是AppClassLoader)去加载具体驱动实现类。
-
-
模块化/热部署/OSGi: 应用服务器(如 Tomcat)需要为不同的 Web 应用提供独立的类空间(避免应用间类冲突)。OSGi 框架提供更精细的模块化。
-
解决方法: 自定义类加载器,直接覆盖
loadClass()方法(如WebappClassLoader),先尝试自己加载(优先加载 WEB-INF/lib 下的类),加载不了再委派父加载器。这实际违反了标准的“先父后子”顺序,变成了“先子后父”。
-
-
覆盖基础类行为 (不推荐): 极少数情况下,需要替换核心类的行为(通常不安全)。
-
解决方法: 自定义类加载器覆盖
findClass()方法甚至loadClass(),直接加载自定义的核心类实现。
-
总结:
双亲委派机制是 Java 类加载体系的灵魂。它通过层次化的委托模型,确保了:
-
核心类库由顶层加载器加载,安全且唯一。
-
用户自定义类无法冒充核心类。
-
类加载有优先级,职责清晰(核心 -> 扩展 -> 应用)。
-
避免了类的重复加载(父加载器优先)。
理解双亲委派是深入理解 Java 类加载过程、解决类加载冲突 (ClassNotFoundException, NoClassDefFoundError, LinkageError) 以及学习高级模块化框架(OSGi, Java 9+ JPMS)的基础。虽然在某些复杂场景下(如 SPI, OSGi)需要打破它,但这正是为了解决在这些场景下严格遵循委派带来的限制。

浙公网安备 33010602011771号