双亲委派机制的特点

双亲委派机制确保类加载时,首先由顶层的启动类加载器加载,避免内存中出现多份同样的字节码,保护核心API不受篡改。若父加载器无法加载,则子加载器尝试加载。该机制在防止自定义`System`类等核心类被替换方面起到关键作用。自定义类加载器可以绕过委托机制,加载特定目录下的类。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

双亲委派机制:

image-20200524145050497

1. 原理:

  1. 如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行;
  2. 如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器;
  3. 如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式。

如果程序中没有定义过自己的类加载器,一般情况下,应用程序加载类加载器就是这个程序的默认加载器。

2.双亲委派模型的好处

1. 防止内存中出现多份同样的字节码
比如两个类A和类B都要加载System类:

  • 如果不用委托而是自己加载自己的,那么类A就会加载一份System字节码,然后类B又会加载一份System字节码,这样内存中就出现了两份System字节码。
  • 如果使用委托机制,会递归的向父类查找,也就是首选用Bootstrap尝试加载,如果找不到再向下。这里的System就能在Bootstrap中找到然后加载,如果此时类B也要加载System,也从Bootstrap开始,此时Bootstrap发现已经加载过了System那么直接返回内存中的System即可而不需要重新加载,这样内存中就只有一份System的字节码了。

2. 防止内存中出现多份同样的字节码

保护程序安全,防止核心API被随意篡改。

  • 例如:如果我们自己自定义了一个 java.lang.String 的类时,如果没有双亲委派机制的话,那么就会将我们程序中的所有运用到String 类 都会使用成我们自定义的类,那么程序就会崩了。有谱
  • 有了双亲委派机制后,首先应用程序类加载器接收到 java.lang.String 的请求之后,就会把这个请求委派给它的父类加载器,即 拓展类加载器。 拓展类加载器收到请求后,又会委派给它的父类加载器,即 引导类加载器。引导类加载器检查了这个类的 包名为 java.lang,那么引导类加载器就会对 它进行加载,因为java.lang 是 Java 下的核心类库。这时候就不会交给系统类加载器进行加载了。即便我们写了这个类,但它也是一个无效类,从而提高了安全性。

3. 双亲委派模型注意点:

  1. Java虚拟机的第一个类加载器是Bootstrap,这个加载器很特殊,它不是Java类,因此它不需要被别人加载,它嵌套在Java虚拟机内核里面,也就是JVM启动的时候Bootstrap就已经启动,它是用C++写的二进制代码(不是字节码),它可以去加载别的类。

    这也是我们在测试时为什么发现System.class.getClassLoader()结果为null的原因,这并不表示System这个类没有类加载器,而是它的加载器比较特殊,是BootstrapClassLoader,由于它不是Java类,因此获得它的引用肯定返回null。

  2. 委托机制具体含义
    当Java虚拟机要加载一个类时,到底派出哪个类加载器去加载呢?

    • 首先当前线程的类加载器去加载线程中的第一个类(假设为类A)。
      注:当前线程的类加载器可以通过Thread类的getContextClassLoader()获得,也可以通过setContextClassLoader()自己设置类加载器。
    • 如果类A中引用了类B,Java虚拟机将使用加载类A的类加载器去加载类B。
    • 还可以直接调用ClassLoader.loadClass()方法来指定某个类加载器去加载某个类。

4. 能不能自己写个类叫java.lang.System

  • **答案:**通常不可以,但可以采取另类方法达到这个需求。
    **解释:**为了不让我们写System类,类加载采用委托机制,这样可以保证爸爸们优先,爸爸们能找到的类,儿子就没有机会加载。而System类是Bootstrap加载器加载的,就算自己重写,也总是使用Java系统提供的System,自己写的System类根本没有机会得到加载。

    但是,我们可以自己定义一个类加载器来达到这个目的,为了避免双亲委托机制,这个类加载器也必须是特殊的。由于系统自带的三个类加载器都加载特定目录下的类,如果我们自己的类加载器放在一个特殊的目录,那么系统的加载器就无法加载,也就是最终还是由我们自己的加载器加载。

<think>嗯,用户这次的问题相当专业,聚焦在JDK8和JDK9的类加载器架构差异,以及双亲委派模型的实践应用上。从提问深度来看,用户应该是有一定Java底层知识的开发者,可能正在处理模块化迁移或Tomcat优化相关的工作。 用户特别强调要对比JDK8和JDK9,这需要我精确区分两个版本的关键变化。引用[2]和[3]提到JDK9的模块化系统是分水岭:平台类加载器取代扩展加载器、所有内置加载器继承BuiltinClassLoader、新增模块化加载逻辑。这些在架构图中必须突出呈现。 关于双亲委派模型的用途,用户似乎更关注实践意义而非理论。除了基础的安全隔离和类唯一性,要补充Tomcat的实战案例(如webapp隔离)来说明“打破”的必要性。引用[1]提到的类相等性判断也值得展开——这在OSGi动态加载场景至关重要。 Tomcat部分需要结构化说明:它的层级设计(Common/Webapp隔离)、并行加载优化、热部署实现原理,都要关联到双亲委派的突破点。尤其要强调WebappClassLoader如何通过重写loadClass()实现应用隔离,这点用户可能在调优时实际遇到过。 最后生成的问题要引导向深度实践:SPI机制和JDK9的模块化冲突是个经典坑点,自定义类加载器实现则是高级开发必备技能。用户既然问到Tomcat,可能对中间件设计也感兴趣。</think>以下是针对JDK8与JDK9类加载器架构、双亲委派模型对比及其应用场景的详细分析: --- ### 一、JDK8与JDK9类加载器架构对比 | **特性** | **JDK8** | **JDK9(引入模块化)** | |------------------|-----------------------------------|-------------------------------------| | **顶层加载器** | Bootstrap ClassLoader | Bootstrap ClassLoader | | **扩展加载器** | Extension ClassLoader | **平台类加载器(Platform ClassLoader)** | | **应用加载器** | Application ClassLoader | Application ClassLoader | | **继承关系** | 各加载器独立继承URLClassLoader | 所有内置加载器继承`jdk.internal.loader.BuiltinClassLoader`[^3] | | **核心变化** | 基于JAR包和类路径加载 | **模块化加载**:按模块依赖关系加载类 | > **关键差异**:JDK9用**平台类加载器**替代扩展加载器,并重构为模块化架构。所有内置加载器统一继承`BuiltinClassLoader`,实现模块化隔离和资源访问控制[^3]。 --- ### 二、双亲委派模型的工作流程(JDK8 vs JDK9) #### **JDK8 经典流程**: 1. 子加载器收到请求 → 委派父加载器 2. 父加载器尝试加载(失败则向下传递) 3. 最终由子加载器调用`findClass()`加载 > **特点**:严格自顶向下委派(Bootstrap→Extension→Application) #### **JDK9 模块化流程**: 1. 加载请求 → **先搜索当前加载器的具名模块**(如平台类加载器检查模块路径) 2. 若模块中未找到 → 委派父加载器 3. 父加载器失败 → 子加载器尝试加载 > **变化**:**优先模块内加载**,打破严格的自顶向下链条[^2]。 --- ### 三、双亲委派模型的四大核心用途 1. **避免类重复加载** 高层加载器(如Bootstrap)加载的类(如`java.lang.String`)不会被低层重复加载,确保类唯一性。 2. **保障核心库安全** 防止用户伪造核心类(如自定义`java.lang.Object`被高层加载器拦截)[^1]。 3. **隔离类冲突** 不同加载器加载的同名类(如`com.example.Foo`)被JVM视为不同类,避免冲突[^1]。 4. **资源高效利用** 按优先级委派减少冗余搜索,提升加载效率。 --- ### 四、打破双亲委派的场景与方法 #### **常见场景**: 1. **SPI机制(如JDBC驱动加载)** - **问题**:Bootstrap加载器无法加载`java.sql.Driver`实现类(位于应用路径) - **方案**:使用线程上下文类加载器(`ThreadContextClassLoader`)反向委派[^2]。 2. **热部署/热加载(如Tomcat)** - **需求**:独立加载每个Web应用的类,支持应用级隔离和重启。 3. **模块化动态加载(如OSGi)** - **需求**:允许不同模块使用同名类的独立版本。 #### **打破方法**: ```java // 自定义类加载器重写loadClass()打破委派 public class CustomClassLoader extends ClassLoader { @Override protected Class<?> loadClass(String name, boolean resolve) { // 1. 优先自己加载(跳过委派父类) Class<?> c = findLoadedClass(name); if (c == null) { c = findClass(name); // 直接加载目标类 } return c; } } ``` --- ### 五、Tomcat打破双亲委派的原因与优势 #### **原因**: 1. **应用隔离需求** 多个Web应用可能依赖**同名但版本冲突**的库(如Spring 4.x vs 5.x)。 2. **独立热部署** 单个应用重启时,需卸载其所有类而不影响其他应用。 3. **安全性隔离** 防止恶意Web应用篡改核心类或访问其他应用数据。 #### **实现方案**: - **自定义WebappClassLoader**:每个Web应用独享一个类加载器。 - **层级设计**: ```mermaid graph TD Bootstrap --> Common[Common ClassLoader] Common --> Catalina[Server ClassLoader] Common --> Shared[Shared ClassLoader] Shared --> Webapp1[WebappClassLoader A] Shared --> Webapp2[WebappClassLoader B] ``` - **Common**:加载Tomcat核心库(所有Webapp共享) - **WebappClassLoader**:优先加载`/WEB-INF/classes`和`/WEB-INF/lib`中的应用私有类**(不委派父加载器)** #### **优势**: - ✅ **类隔离**:应用A的`lib/foo.jar v1.0`与应用B的`lib/foo.jar v2.0`互不影响。 - ✅ **热部署**:重启应用时,丢弃其专属`WebappClassLoader`并新建实例。 - ✅ **资源高效**:共享核心库(如Servlet API)仅由Common加载一次。 --- ### 总结 | **维度** | **JDK8** | **JDK9** | **Tomcat实践** | |---------------|-----------------------|------------------------|------------------------| | **架构核心** | 类路径委派 | 模块化优先加载 | 应用级类加载器隔离 | | **安全性** | 核心库保护 | 模块访问控制 | 防止跨应用类篡改 | | **灵活性** | SPI需打破委派 | 内置模块化支持 | 热部署无需重启容器 | | **典型场景** | 传统JAR应用 | 模块化项目 | 多租户Web容器 | > 双亲委派是Java安全的基石,但在模块化、隔离性需求下,合理打破它才能实现更高灵活性[^2][^3]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值