当前位置: 首页 > news >正文

Java 双亲委派机制笔记

什么是双亲委派机制

双亲委派机制(Parent Delegation Model)是 Java 类加载器的一种设计模式。在该模式下,类加载器在加载类时会首先把请求交给父加载器加载,父加载器无法完成加载请求时,才由当前加载器尝试自己去加载。

这一机制是 Java 保证类加载安全性避免重复加载核心类类一致性的关键机制。

类加载过程简介

在 Java 中,类的生命周期分为以下几个阶段:

  1. 加载(Loading)
  2. 验证(Verification)
  3. 准备(Preparation)
  4. 解析(Resolution)
  5. 初始化(Initialization)
  6. 使用(Using)
  7. 卸载(Unloading)

双亲委派机制影响的是“加载阶段”,即类从 .class 文件或字节码中被 JVM 加载为 Class 对象的过程。

Java 类加载器的分类

Java 中的类加载器分为以下几种(逻辑上是层级结构,但在 JVM 内部实现上是组合):

1. 启动类加载器(Bootstrap ClassLoader)

  • JVM 自身的一部分,用 C/C++ 实现。
  • 负责加载 JDK 核心类库,如 rt.jarmodules/java.base 中的类(如 java.lang.*java.util.* 等)。
  • 加载路径来自 -Xbootclasspath

2. 扩展类加载器(Extension ClassLoader)

  • 用 Java 实现,父加载器为 Bootstrap。
  • 加载 JAVA_HOME/lib/ext/ 目录或 java.ext.dirs 指定的目录下的类库。

3. 应用类加载器(App ClassLoader / System ClassLoader)

  • 加载 classpath 下的类(通常是用户的应用代码)。
  • 是大多数 Java 程序默认使用的加载器。

4. 自定义类加载器(User-defined ClassLoader)

  • 用户可以通过继承 ClassLoaderURLClassLoader 实现。
  • 可用于隔离、热加载、插件系统等高级功能。

类加载器的父子关系图(逻辑结构)

                 [Bootstrap ClassLoader]↑[Extension ClassLoader]↑[Application ClassLoader]↑[User-defined ClassLoader]

注意:这种关系是“逻辑上的委派”,实际的实现并非严格的继承,而是通过组合(如构造函数传入父加载器引用)。

双亲委派模型的工作机制

Java 中每个类加载器在加载类时会遵循如下逻辑:

  1. 检查是否已加载该类(findLoadedClass)
  2. 委托父加载器尝试加载(parent.loadClass)
  3. 如果父加载器找不到,则当前类加载器调用 findClass() 自己尝试加载

模拟流程图

loadClass(className):if (hasLoaded(className)) {return loadedClass;} else {try {return parent.loadClass(className);} catch (ClassNotFoundException e) {return findClass(className);}}

这种从上到下的递归式加载,构成了“双亲优先”的委派模型。

双亲委派的优点

1. 避免类重复加载

例如,不同模块都定义了 java.lang.String,双亲委派确保只加载系统提供的 String 类。

2. 安全性

核心类优先由 Bootstrap 加载,防止恶意代码替换标准 API。

3. 保持类型一致性

类加载器不同,JVM 会认为加载出来的类是不同的,哪怕类名、包名完全一致。双亲委派可以避免多个类加载器加载同一类导致的 ClassCastException

双亲委派源码解析(ClassLoader#loadClass()

protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {// 1. 首先检查是否已经加载Class<?> c = findLoadedClass(name);// 2. 没加载则委托父加载器if (c == null) {try {if (parent != null) {c = parent.loadClass(name, false); // 递归向上委派} else {c = findBootstrapClassOrNull(name); // Bootstrap 加载器处理}} catch (ClassNotFoundException e) {// 忽略,由当前加载器自己加载}}// 3. 父类加载器未加载成功,则由当前类加载器尝试加载if (c == null) {c = findClass(name); // 需要用户在子类中实现}// 4. 如果需要解析类if (resolve) {resolveClass(c);}return c;
}

打破双亲委派机制的情况

尽管双亲委派机制带来了安全性和一致性,但在某些实际场景下,会刻意“打破”这一机制。

常见打破机制的场景

场景描述
Web 应用容器(如 Tomcat)每个应用有独立类加载器,避免类冲突
插件系统 / 模块系统(OSGi)每个插件或模块有自己类加载器
JDBC SPI机制接口由引导加载器加载,实现类由 AppClassLoader 加载
热部署 / 热加载需重复加载不同版本类,必须打破委派

打破方式

  • 重写 loadClass 方法,不调用 super.loadClass() 或先自己加载再委派。
  • 使用 Thread.currentThread().getContextClassLoader() 代替类自身加载器。
  • 使用 URLClassLoader 动态加载 JAR。

示例:SPI机制如何打破双亲委派

SPI接口(如 java.sql.Driver)是由 Bootstrap 加载的,但其实现类(如 com.mysql.jdbc.Driver)在用户类路径上,因而无法由 Bootstrap 加载。

解决方法是使用线程上下文类加载器:

Thread.currentThread().setContextClassLoader(MyClassLoader);

自定义类加载器示例

以下是一个基本的自定义类加载器例子:

public class MyClassLoader extends ClassLoader {private final String basePath;public MyClassLoader(String basePath) {this.basePath = basePath;}@Overrideprotected Class<?> findClass(String name) throws ClassNotFoundException {String path = basePath + name.replace(".", "/") + ".class";try {byte[] classBytes = Files.readAllBytes(Paths.get(path));return defineClass(name, classBytes, 0, classBytes.length);} catch (IOException e) {throw new ClassNotFoundException(name);}}
}

类加载器与类型隔离

在 JVM 中,类是由类的“全限定名 + 加载它的类加载器”决定唯一性的

if (A.class loaded by Loader1 != A.class loaded by Loader2) {instanceoffalseequals → falsetype cast → ClassCastException
}

这就是插件系统、容器中的类隔离依据。

常见面试问题解析

Q1:什么是双亲委派机制?

是 Java 类加载器在加载类时将加载请求先委托给父类加载器处理,直到 Bootstrap 加载器,只有当父类加载器无法完成加载时,才由当前类加载器处理的一种模型。

Q2:为什么需要双亲委派机制?

为了防止重复加载、类冲突和核心类被篡改,同时也提升安全性和一致性。

Q3:有哪些场景打破了双亲委派机制?

Tomcat 的 WebAppClassLoader、SPI 服务接口机制、插件式架构、Spring Boot DevTools 的热部署等。

Q4:类加载器是线程安全的吗?

是线程安全的,JVM 在加载类时会对类加载过程进行加锁。

Q5:类加载器如何影响类的唯一性?

JVM 通过“类名 + 加载器”唯一标识一个类,不同加载器加载的同名类被视为不同。


总结

  • Java 中类的加载采用了 双亲委派模型,即先由父类加载器加载,加载失败后才自己加载。
  • 类加载器分为:启动类加载器、扩展类加载器、应用类加载器和自定义类加载器。
  • 双亲委派机制带来了安全性、一致性和防重复性,是 Java 平台的重要基石。
  • 某些场景如 SPI、Web 容器、热部署系统中,需要打破或改写该模型。
  • 理解类加载器和双亲委派机制对于掌握 Java 虚拟机原理、性能优化、框架底层原理(如 Spring、Tomcat、Netty)非常重要。
http://www.dtcms.com/a/267877.html

相关文章:

  • GitCode项目创建指南
  • 一文掌握Qt Quick数字图像处理项目开发(基于Qt 6.9 C++和QML,代码开源)
  • 【黑马点评】(二)缓存
  • PyTorch 2.7深度技术解析:新一代深度学习框架的革命性演进
  • Python作业1
  • 实现Spring MVC登录验证与拦截器保护:从原理到实战
  • Jiraph​ 简介
  • React 各颜色转换方法、颜色值换算工具HEX、RGB/RGBA、HSL/HSLA、HSV、CMYK
  • AcWing--873.欧拉函数
  • ARMv8 创建1、2、3级页表代码与注释
  • 【C++基础】内存管理四重奏:malloc/free vs new/delete - 面试高频考点与真题解析
  • Windows 11 Enterprise LTSC 转 IoT
  • C++ i386/AMD64平台汇编指令对齐长度获取实现
  • LangChain:构建一个Agent(入门篇四)
  • [leetcode] C++ 并查集模板
  • 【机器学习笔记 Ⅱ】1 神经网络
  • 云原生 Serverless 架构下的智能弹性伸缩与成本优化实践
  • 基于HTML与Java的简易在线会议系统实现
  • Javaweb - 10.5 HttpServletRequest 和 HttpServletResponse
  • Flink ClickHouse 连接器维表源码深度解析
  • 【Note】《Kafka: The Definitive Guide》第四章:Kafka 消费者全面解析:如何从 Kafka 高效读取消息
  • 深入理解Kafka幂等性:原理、边界与最佳实践
  • Neo4j 综合练习作业
  • Android 应用开发 | 一种限制拷贝速率解决因 IO 过高导致系统卡顿的方法
  • java ThreadLocal源码分析
  • 深度学习6(多分类+交叉熵损失原理+手写数字识别案例TensorFlow)
  • 高效处理大体积Excel文件的Java技术方案解析
  • 安卓之service
  • QT 菜单栏设计使用方法
  • 基于AndServer的RPC架构:Android原生SO文件远程调用实战指南