当前位置: 首页 > 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.lryc.cn/news/581676.html

相关文章:

  • QML 使用QtObject定义私有变量
  • 基于Flask和机器学习开发的米其林餐厅数据可视化平台
  • 单片机:STM32F103的开发环境搭建
  • 单片机物联网应用中的 Pogopin、串口与外围模组通信技术解析
  • ABP VNext + Tye:本地微服务编排与调试
  • 基于udev规则固定相机名称
  • [netty5: WebSocketServerHandshaker WebSocketServerHandshakerFactory]-源码分析
  • 桥梁桥拱巡检机器人cad+【4张】设计说明书+绛重+三维图
  • 力扣 hot100 Day36
  • webUI平替应用,安装简单,功能齐全
  • LeetCode 75. 颜色分类(荷兰国旗问题)
  • 服务端向客户端主动推送数据的几种方法(Spring Boot 环境)
  • 11.进程间通信
  • VSCode+arm-none-eabi-gcc交叉编译+CMake构建+OpenOCD(基于Raspberry Pico RP2040)
  • 2.线性神经网络--Softmax回归
  • 算法分析与设计实验1:实现两路合并排序和折半插入排序
  • 3.8 java连接数据库
  • Vue2 day07
  • 工业相机和镜头
  • 基于Java+SpringBoot的医院信息管理系统
  • ARM 学习笔记(一)
  • 文心开源大模型ERNIE-4.5-0.3B-Paddle私有化部署保姆级教程及技术架构探索
  • 【学习笔记】4.1 什么是 LLM
  • 编程语言艺术:C语言中的属性attribute笔记总结
  • 程序员在线接单
  • 浅谈漏洞扫描与工具
  • 大型语言模型中的自动化思维链提示
  • 【数据分析】R语言多源数据的基线特征汇总
  • 玄机——第三章 权限维持-linux权限维持-隐藏练习
  • Dify+Ollama+QwQ:3步本地部署,开启AI搜索新篇章