JVM 的双亲委派模型(Parent Delegation Model)是类加载机制中的一个重要组成部分,它不是强制性的约束,而是一种推荐的类加载器实现方式。它对于保证 Java 程序的安全性、稳定性和避免类冲突至关重要。
双亲委派模型的工作原理:
- 委托父加载器: 当一个类加载器需要加载类时,它首先不会自己尝试加载,而是 委托给它的父类加载器 去加载。
- 递归委托: 这个委托过程是递归的,直到到达最顶层的启动类加载器(Bootstrap Class Loader)。
- 父加载器尝试加载: 父类加载器会尝试在其负责的范围内加载该类。如果父类加载器能够加载,则加载成功,并返回
Class对象。 - 子加载器尝试加载: 如果父类加载器无法加载该类(在其搜索范围内找不到该类,或者抛出
ClassNotFoundException),则 将加载请求返回给子类加载器。 - 子加载器加载: 子类加载器尝试加载该类。如果子类加载器能够加载,则加载成功,并返回
Class对象。 - 加载失败: 如果子类加载器也无法加载该类,则抛出
ClassNotFoundException异常。
双亲委派模型图示:
+-----------------------------+
| Bootstrap Class Loader | (C/C++) 加载核心类库
+-----------------------------+
↑
| (Parent) 委托
|
+-----------------------------+
| Extension Class Loader | 加载扩展类库
+-----------------------------+
↑
| (Parent) 委托
|
+-----------------------------+
| Application Class Loader | 加载应用程序类
+-----------------------------+
↑
| (Parent) 委托
|
+-----------------------------+
| User-Defined Class Loader | 自定义类加载器
+-----------------------------+
JVM 需要双亲委派模型的原因和好处:
-
保证 Java 核心类库的安全性 (Security):
- 防止篡改: 双亲委派模型可以防止用户自定义的类加载器加载或替换 Java 核心类库中的类(例如
java.lang.Object、java.lang.String)。因为核心类库总是由启动类加载器加载,而启动类加载器是用 C/C++ 实现的,位于 JVM 内部,用户代码无法访问。 - 沙箱机制: 这相当于构建了一个 Java 类的沙箱环境,确保核心类库的安全性,防止恶意代码通过替换核心类来破坏 JVM 或操作系统。
- 防止篡改: 双亲委派模型可以防止用户自定义的类加载器加载或替换 Java 核心类库中的类(例如
-
避免类的重复加载 (Efficiency and Consistency):
- 唯一性: 双亲委派模型确保同一个类只会被加载一次。如果一个类已经被父类加载器加载过,子类加载器就不会再加载它,而是直接使用父类加载器加载的
Class对象。 - 避免冲突: 避免了不同类加载器加载同一个类而导致的命名冲突和类型转换错误。
- 节省资源: 避免了重复加载类所带来的内存和 CPU 资源浪费。
- 唯一性: 双亲委派模型确保同一个类只会被加载一次。如果一个类已经被父类加载器加载过,子类加载器就不会再加载它,而是直接使用父类加载器加载的
-
保证类的加载顺序 (Order):
- 双亲委派模型保证了类的加载顺序是自顶向下的,即核心类库优先于扩展类库,扩展类库优先于应用程序类。
- 这种加载顺序有助于维护类之间的依赖关系,避免出现类找不到或版本冲突的问题。
-
实现命名空间隔离 (Namespace Isolation):
- 不同的类加载器加载的类位于不同的命名空间中。即使两个类加载器加载了同一个类(例如,两个不同的 Web 应用程序加载了同一个 Servlet 类),它们也是不同的类,不会相互干扰。
- 这对于 Web 应用程序、OSGi 模块化系统等场景非常重要,可以避免不同应用程序或模块之间的类冲突。
总结双亲委派模型的好处:
- 安全性: 保护 Java 核心类库不被篡改,构建 Java 类的沙箱环境。
- 一致性: 避免类的重复加载,保证同一个类在 JVM 中只有一份
Class对象。 - 避免冲突: 避免不同类加载器加载同一个类导致的命名冲突和类型转换错误。
- 有序性: 保证类加载的顺序(核心类库 > 扩展类库 > 应用程序类)。
- 隔离性: 不同类加载器加载的类位于不同的命名空间,实现隔离。
双亲委派模型的局限性和破坏:
虽然双亲委派模型有很多优点,但在某些情况下,可能需要破坏双亲委派模型,例如:
-
SPI (Service Provider Interface):
- Java 中的 SPI 机制(例如 JDBC、JNDI、JAXP 等)允许第三方提供服务的实现。
- SPI 的核心接口通常由启动类加载器加载,但具体的实现类通常由应用程序类加载器或自定义类加载器加载。
- 为了解决这个问题,Java 引入了线程上下文类加载器 (Thread Context ClassLoader)。可以通过
Thread.currentThread().getContextClassLoader()获取线程上下文类加载器。
-
OSGi (Open Service Gateway initiative):
- OSGi 是一个模块化系统,每个模块(bundle)都有自己的类加载器。
- OSGi 的类加载器模型不是严格的双亲委派模型,而是更复杂的网状结构。
-
热部署 (HotSwap):
- 在应用程序运行时,动态地替换或更新类。
- 通常需要自定义类加载器,并破坏双亲委派模型。
-
Tomcat:
- Tomcat 为了实现 web 应用之间的隔离,以及共享库的加载,破坏了双亲委派模型。
- 每个 web 应用都有自己的类加载器 (WebAppClassLoader)。
总结:
双亲委派模型是 JVM 类加载机制的核心,它通过委托父类加载器加载类的方式,保证了 Java 程序的安全性、稳定性和避免类冲突。 虽然双亲委派模型在大多数情况下都能很好地工作,但在某些特殊情况下(例如,SPI、OSGi、热部署),可能需要破坏双亲委派模型来实现特定的功能。
1772

被折叠的 条评论
为什么被折叠?



