news 2026/1/15 20:42:57

深入理解类加载器

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
深入理解类加载器

目录

一、回忆类加载过程

二、类加载器

1、定义与本质

2、JVM内置类加载体系

3、自定义类加载器

ClassLoader类中的两个关键方法:

核心原则:

4、类加载器加载的顺序

(1)核心:双亲委派模型下的委托与加载顺序

1)委托阶段(向上传递请求)

2)加载阶段(向下尝试加载)

注意:

(2)内置类加载器的层级加载范围顺序

(3)触发时机顺序

1) 触发类加载的常见时机

2)依赖类的加载顺序

三、双亲委派模型

1、什么是“双亲”

2、定义

3、执行流程

(1)步骤 1:触发加载请求

(2)步骤 2:向上委托

(3)步骤 3:顶层加载器自检查

(4)步骤 4:向下回传失败结果

(5)步骤 5:当前加载器自行加载

可视化流程:

4、底层实现(基于ClassLoader源码)

(1)loadClass()方法的核心源码(JDK8)

(2)核心方法

5、双亲委派模型的优点

(1)保证 Java 核心类的安全,防止核心 API 被篡改

(2)保证类的唯一性,避免重复加载

(3)明确类加载器的职责划分,提高加载效率

6、打破双亲委派模型

(1)场景一:Tomcat 等 Web 容器的类加载

问题背景

解决方案:打破双亲委派

(2)场景二:Java SPI(服务提供者接口)的加载

问题背景

解决方案:线程上下文类加载器(Thread Context ClassLoader)

(3)场景三:热部署 / 热加载

问题背景

解决方案:打破双亲委派的缓存机制

(4)场景四:Java 9 的模块化系统


一、回忆类加载过程

类加载器是类加载过程中加载过程中核心的实现,如果不熟悉类加载过程,可参考下面链接中的理解类加载过程回忆一下类加载的过程。

理解类加载过程https://blog.csdn.net/2201_75450136/article/details/155944812?spm=1001.2014.3001.5501


二、类加载器

1、定义与本质

类加载器(ClassLoader)是 Java 虚拟机(JVM)的核心组件之一,其核心职责是实现类加载过程中的 “加载” 阶段:根据类的全限定名(如java.lang.Stringcom.example.User),从磁盘、网络、jar 包等数据源中查找并读取对应的.class字节码文件,将其转换为 JVM 内存中的java.lang.Class对象(该对象是类在 JVM 中的唯一标识,后续对类的所有操作都通过这个对象进行)。

注意:

  • 类加载器仅负责加载阶段,而链接(验证、准备、解析)和初始化阶段由 JVM 自身完成;
  • 除了启动类加载器,其他类加载器本身也是 Java 类,遵循 “被其他类加载器加载” 的规则;
  • JVM 中类的唯一性由 “类加载器 + 类的全限定名” 共同决定—— 即使两个类的全限定名相同,只要由不同的类加载器加载,JVM 就会将其视为两个完全不同的类,这也是类隔离的核心基础。

2、JVM内置类加载体系

JVM 提供了三层内置类加载器,分别负责加载不同来源的类,其层级关系和职责清晰划分:

类加载器类型全称 / 别称实现语言与归属核心加载范围关键特点
启动类加载器(Bootstrap ClassLoader)引导类加载器C/C++ 实现,属于 JVM 内核组件JVM 安装目录下的lib文件夹中核心类库(如rt.jarcharsets.jar),仅加载符合java.*包的核心类1. 不是 Java 类,无法通过代码获取其实例(getClassLoader()返回null);2. 最高层级的类加载器
扩展类加载器(Extension ClassLoader)平台类加载器(Java 9+)Java 实现(sun.misc.Launcher$ExtClassLoader),属于 JDK 类库JVM 安装目录下的lib/ext文件夹,或通过java.ext.dirs系统属性指定的目录中的类库1. 是启动类加载器的子加载器;2. Java 9 后更名为平台类加载器,加载范围扩展到系统模块
应用类加载器(Application ClassLoader)系统类加载器(System ClassLoader)Java 实现(sun.misc.Launcher$AppClassLoader),属于 JDK 类库项目的类路径(Classpath,包括src编译后的.class 文件、第三方 jar 包如 Spring/MyBatis)1. 是扩展类加载器的子加载器;2. 开发者自定义类的默认加载器

平台类加载器

Java 9 引入模块化(Module)后,对类加载器体系做了微调:

  • 移除了扩展类加载器,替换为平台类加载器(Platform ClassLoader),负责加载 JDK 的系统模块和扩展类;
  • 启动类加载器负责加载核心模块(如java.base);
  • 应用类加载器负责加载应用模块和 Classpath 中的类;
  • 整体层级关系不变,仍遵循双亲委派的核心逻辑。

3、自定义类加载器

当内置类加载器无法满足特殊需求时(如加载加密的.class 文件、从网络下载字节码、动态生成类),可以通过继承ClassLoader类实现自定义类加载器。

ClassLoader类中的两个关键方法:

  • protected Class loadClass(String name, boolean resolve):加载指定二进制名称的类,且实现了双亲委派机制 。其中name为类的二进制名称,当resolve的值为 true,在加载时调用resolveClass(Class<?> c)方法解析该类。
  • protected Class findClass(String name):是根据类的二进制名称来查找类,默认实现的是空方法。

核心原则:

  • 不重写loadClass()方法:避免破坏双亲委派模型,仅需重写findClass()方法(实现自定义的类查找逻辑);
  • 通过defineClass()生成 Class 对象:将读取到的字节码数组转换为Class对象,这是 JVM 提供的底层方法,保证类的合法加载。

4、类加载器加载的顺序

类加载器的加载顺序并非单一的 “线性顺序”,而是结合了双亲委派模型的 “委托顺序”、类加载器的 “层级顺序” 以及触发类加载的 “时机顺序”三个维度。

(1)核心:双亲委派模型下的委托与加载顺序

这是类加载器最基础、最核心的加载顺序,也是单个类被加载时的核心流程。简单来说,顺序是:先向上委托父加载器,再向下由子加载器自行加载,具体可分为「委托阶段」和「加载阶段」两个步骤。

1)委托阶段(向上传递请求)

当任意一个类加载器收到类加载请求时,会按 “当前类加载器 → 父类加载器 → 启动类加载器”的顺序,将请求向上委托,直到传递到最顶层的启动类加载器。

示例:加载com.example.User时的委托顺序:

应用类加载器(AppClassLoader)→ 扩展类加载器(ExtClassLoader/PlatformClassLoader)→ 启动类加载器(BootstrapClassLoader)
2)加载阶段(向下尝试加载)

当父加载器无法加载该类时,请求会向下回传,由子加载器依次尝试自行加载,直到某个加载器成功加载或全部失败(抛出ClassNotFoundException)。

示例:加载com.example.User时的加载顺序:

启动类加载器(检查核心类库,失败)→ 扩展类加载器(检查扩展目录,失败)→ 应用类加载器(检查Classpath,成功加载)
注意:
  • 委托阶段是“单向向上”的,加载阶段是“单向向下”的;
  • 每个类加载器在委托前,会先检查自身是否已经加载过该类(缓存机制),避免重复加载。

(2)内置类加载器的层级加载范围顺序

从 JVM 内置类加载器的职责划分来看,它们的加载范围有明确的优先级顺序启动类加载器优先加载核心类,其次是扩展 / 平台类加载器,最后是应用类加载器。这种顺序是为了保证核心类的唯一性和安全性。

加载优先级类加载器类型加载范围优先级说明
最高启动类加载器核心类库(java.*先加载 JVM 最核心的类
中间扩展 / 平台类加载器扩展类库次加载系统扩展类
最低应用类加载器应用类和第三方库最后加载开发者自定义的类

举例:当同时存在java.lang.String(核心类)、com.sun.nio.file.ExtendedFileSystem(扩展类)、com.example.User(应用类)时,加载顺序为:

java.lang.String(启动类加载器)→com.sun.nio.file.ExtendedFileSystem(扩展类加载器)→com.example.User(应用类加载器)。

(3)触发时机顺序

类加载器并非启动时就加载所有类,而是“懒加载”(按需加载),只有在特定时机才会触发类加载,不同触发时机的加载顺序遵循“使用即加载”的原则,且存在一些固定的先后依赖。

1) 触发类加载的常见时机
  • 第一次创建类的实例new User()):触发类加载;
  • 第一次访问类的静态变量 / 静态方法User.numUser.test()):触发类加载;
  • 反射调用类Class.forName("com.example.User")):主动触发类加载;
  • 加载子类时:先加载父类(父类的加载顺序优先于子类);
  • 执行main()方法的类:作为程序入口,最先被加载;
  • SPI 服务加载(如ServiceLoader.load(XXX.class)):触发实现类的加载。
2)依赖类的加载顺序

当一个类依赖其他类时,加载顺序为:被依赖的类优先于依赖类加载

如:

public class Parent {} public class Child extends Parent { private static User user = new User(); }

当第一次加载Child类时,加载顺序为:Parent(父类,由对应加载器加载)→User(依赖类)→Child(子类)。


三、双亲委派模型

双亲委派模型(Parent Delegation Model)是 Java 虚拟机(JVM)中类加载器的核心设计模式,其本质是一种 “向上委托、向下加载” 的层级委派机制,旨在通过严格的加载顺序保证 Java 核心类的安全与唯一性,同时简化类加载的职责划分。

1、什么是“双亲”

“双亲” 不是 “父母”,是 “委派关系”,很多人会误解 “双亲” 是指类加载器的继承关系,实际上:

  • 双亲委派中的 “父类加载器”(Parent ClassLoader)是指 “委派的上级加载器”,而非 Java 中的类继承(extends)关系。
  • 大部分类加载器(如应用类加载器、扩展类加载器)通过组合方式持有父加载器的引用(ClassLoader类中的parent成员变量),而非继承。
  • 唯一的例外是启动类加载器(Bootstrap ClassLoader):它是用 C/C++ 实现的 JVM 内核组件,没有父加载器(parentnull),是整个委派模型的顶层。

2、定义

当一个类加载器收到类加载请求时,它不会立即尝试自己加载,而是遵循以下步骤:

  • 先委托父加载器:将加载请求向上传递给其父加载器处理;
  • 逐层向上委派:这个委托过程会一直传递到最顶层的启动类加载器;
  • 父加载器自检查:每个父加载器会先检查自己是否已经加载过该类,若已加载则直接返回Class对象;若未加载,则在自己的加载范围内查找对应的.class文件;
  • 向下回传失败:如果父加载器在自己的加载范围内找不到该类,会将 “加载失败” 的结果回传给子加载器;
  • 子加载器自行加载:只有当所有父加载器都加载失败后,当前类加载器才会尝试在自己的加载范围内查找并加载该类;
  • 最终失败抛出异常:如果当前类加载器也无法加载,则抛出ClassNotFoundException

3、执行流程

以加载开发者自定义的com.example.User类为例,结合 JVM 内置的三层类加载器,完整执行流程如下:

(1)步骤 1:触发加载请求

当代码中执行new com.example.User()时,应用类加载器(AppClassLoader)收到加载com.example.User的请求。

(2)步骤 2:向上委托

  1. 应用类加载器首先检查自己是否已加载过com.example.User(通过findLoadedClass()方法),若未加载,则将请求委托给其父加载器 ——扩展类加载器(ExtClassLoader/PlatformClassLoader)
  2. 扩展类加载器同样先检查缓存,若未加载,将请求委托给其父加载器 ——启动类加载器(BootstrapClassLoader)

(3)步骤 3:顶层加载器自检查

启动类加载器检查自己的加载范围(JVM 安装目录下的lib文件夹,如rt.jar),发现没有com.example.User类(核心类库只有java.*javax.*等包的类),因此返回 “加载失败”。

(4)步骤 4:向下回传失败结果

  1. 扩展类加载器收到启动类加载器的失败结果后,检查自己的加载范围(lib/ext文件夹或java.ext.dirs指定的目录),也找不到com.example.User,返回 “加载失败”。
  2. 应用类加载器收到扩展类加载器的失败结果后,开始自行加载。

(5)步骤 5:当前加载器自行加载

应用类加载器在类路径(Classpath)下(如项目的target/classes目录)查找com/example/User.class文件,找到后读取字节码并生成Class对象,完成加载。

可视化流程:

应用类加载器(收到请求) ↓(委托) 扩展类加载器 ↓(委托) 启动类加载器(检查核心类库,失败) ↓(回传失败) 扩展类加载器(检查扩展目录,失败) ↓(回传失败) 应用类加载器(检查Classpath,成功加载)

4、底层实现(基于ClassLoader源码)

双亲委派模型的核心逻辑在java.lang.ClassLoader类的loadClass()方法中实现,这是 JVM 提供的默认实现,所有自定义类加载器都继承了这个逻辑。

(1)loadClass()方法的核心源码(JDK8)

protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { // 1. 加锁:保证多线程下类加载的线程安全,避免重复加载 synchronized (getClassLoadingLock(name)) { // 2. 检查当前类加载器是否已加载该类(缓存优先) Class<?> c = findLoadedClass(name); if (c == null) { // 未加载过 long t0 = System.nanoTime(); try { // 3. 有父加载器则委托父加载器加载 if (parent != null) { c = parent.loadClass(name, false); } else { // 4. 无父加载器(如扩展类加载器),委托启动类加载器 c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // 父加载器加载失败,捕获异常,继续下一步 } // 5. 所有父加载器都失败,调用自身的findClass()方法加载 if (c == null) { long t1 = System.nanoTime(); c = findClass(name); // 性能统计(JVM内部用) sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); sun.misc.PerfCounter.getFindClasses().increment(); } } // 6. 若resolve为true,执行类的解析(链接阶段的解析步骤) if (resolve) { resolveClass(c); } return c; } }

(2)核心方法

方法作用
findLoadedClass(name)检查当前类加载器的缓存,避免重复加载
parent.loadClass(name)委托父加载器加载,实现向上委派
findBootstrapClassOrNull(name)委托启动类加载器加载(仅当parentnull时)
findClass(name)父加载器失败后,当前加载器自行查找类(子类需重写)
resolveClass(c)执行类的解析(链接阶段的最后一步)

5、双亲委派模型的优点

设计双亲委派模型的根本目的是保障 Java 程序的安全性和稳定性,具体体现在以下三点:

(1)保证 Java 核心类的安全,防止核心 API 被篡改

这是最核心的优势。例如,若开发者试图自定义一个java.lang.String类(与 Java 核心类同名),按照双亲委派模型:

  • 加载请求会先传递到启动类加载器;
  • 启动类加载器已经加载了核心的java.lang.String类(来自rt.jar),会直接返回该类的Class对象;
  • 自定义的java.lang.String类永远不会被加载,从而避免了核心类被恶意替换或篡改的风险。

补充:JVM 还会对核心类的包名进行校验(如java.lang包的类只能由启动类加载器加载),即使绕过双亲委派,也会抛出SecurityException

(2)保证类的唯一性,避免重复加载

由于每个类加载器都会先检查缓存,且父加载器优先加载,因此同一个类在 JVM 中只会被同一个类加载器加载一次

例如,两个不同的类加载器的子加载器请求加载com.example.User,最终都会委托到应用类加载器加载,从而保证User类的Class对象在 JVM 中是唯一的,避免了类冲突。

(3)明确类加载器的职责划分,提高加载效率

JVM 的内置类加载器有明确的加载范围:

  • 启动类加载器:负责核心类库;
  • 扩展类加载器:负责系统扩展类;
  • 应用类加载器:负责应用类和第三方库。

双亲委派模型让每个加载器只关注自己的职责范围,无需处理其他范围的类,从而提高了类加载的效率和可维护性。

6、打破双亲委派模型

虽然双亲委派模型是 JVM 的默认规则,但在某些场景下,由于其自身的局限性,需要被打破。这些场景也恰恰体现了 Java 类加载机制的灵活性。

(1)场景一:Tomcat 等 Web 容器的类加载

问题背景

Tomcat 作为 Web 容器,需要实现多 Web 应用的类隔离

  • 不同的 Web 应用可能依赖不同版本的第三方库(如应用 A 用 Spring 5,应用 B 用 Spring 6);
  • 若遵循双亲委派,应用类加载器会只加载一次 Spring 类,导致版本冲突。
解决方案:打破双亲委派

Tomcat 自定义了类加载器体系(如WebappClassLoader),其加载顺序与双亲委派相反

  1. 先加载当前 Web 应用WEB-INF/classes目录下的类;
  2. 再加载当前 Web 应用WEB-INF/lib下的 jar 包中的类;
  3. 最后才委托父加载器(Tomcat 的CommonClassLoader)加载公共类和核心类。

核心目的:让每个 Web 应用拥有独立的类空间,实现类隔离。

(2)场景二:Java SPI(服务提供者接口)的加载

问题背景

SPI 是 Java 的一种服务发现机制(如 JDBC、JNDI、SLF4J),以 JDBC 为例:

  • java.sql.Driver接口由启动类加载器加载(属于核心类库rt.jar);
  • 具体的驱动实现(如 MySQL 的com.mysql.cj.jdbc.Driver)在 Classpath 中,应由应用类加载器加载;
  • 按照双亲委派,启动类加载器无法委托子加载器加载,导致无法找到驱动实现。
解决方案:线程上下文类加载器(Thread Context ClassLoader)

JVM 提供了线程上下文类加载器,允许通过Thread.currentThread().getContextClassLoader()获取当前线程的类加载器(默认是应用类加载器),从而打破双亲委派的层级限制:

  1. DriverManager(启动类加载器加载)通过线程上下文类加载器,获取应用类加载器;
  2. 应用类加载器加载 Classpath 中的 JDBC 驱动实现类;
  3. 驱动类被实例化并注册到DriverManager中。

这是一种“父加载器调用子加载器”的逆向委派,本质上打破了双亲委派的单向委托规则。

(3)场景三:热部署 / 热加载

问题背景

热部署(如 Spring Boot DevTools、JRebel)需要在不重启 JVM 的情况下,动态更新类的字节码。

解决方案:打破双亲委派的缓存机制
  • 自定义类加载器加载新的类字节码;
  • 每次热加载时,创建新的类加载器实例,加载更新后的类;
  • 由于类的唯一性由 “类加载器 + 类名” 决定,新的类加载器会生成新的Class对象,从而实现类的动态替换。

这种方式打破了 “类只被加载一次” 的缓存规则,本质上也是对双亲委派的灵活调整。

(4)场景四:Java 9 的模块化系统

Java 9 引入了模块化(Module)系统,对类加载器体系进行了微调:

  • 移除了扩展类加载器,替换为平台类加载器;
  • 允许模块指定 “导出” 和 “开放” 的包,类加载的范围由模块决定。

虽然整体仍遵循双亲委派的核心逻辑,但模块化系统让类加载的范围更精细,也在一定程度上突破了传统双亲委派的职责划分。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/1/13 20:23:25

腾讯云国际站代理商的定制化技术支持服务的成功案例有哪些?

腾讯云国际站代理商的定制化技术支持服务案例&#xff0c;广泛覆盖电商、金融、游戏、文娱等多个出海核心领域&#xff0c;既解决了企业跨境合规难题&#xff0c;又实现了成本优化与业务效率提升&#xff0c;以下是具体案例详情&#xff1a;电商领域东南亚电商平台合规改造&…

作者头像 李华
网站建设 2026/1/15 12:03:31

Dify可视化编排功能对比传统代码开发的优势

Dify可视化编排如何重塑AI应用开发 在企业争相布局大模型的今天&#xff0c;一个现实问题摆在面前&#xff1a;为什么很多团队投入大量人力开发的AI系统最终却难以上线&#xff1f;答案往往出人意料——不是模型不够强&#xff0c;而是构建方式太原始。大多数项目仍依赖传统编码…

作者头像 李华
网站建设 2025/12/24 14:19:39

以品质之道,养铸铁试验平台之生生不息

铸铁试验平台的国家标准的制定和执行对于整个行业的发展和进步具有重要的推动作用。通过严格执行国家标准&#xff0c;可以有效地提高铸铁试验平台的质量和安全性能&#xff0c;保障相关行业的生产和使用安全。同时&#xff0c;国家标准的制定也可以促进相关行业的技。 铸铁试验…

作者头像 李华
网站建设 2026/1/10 0:10:03

LobeChat能否实现AI铸剑师?冷兵器工艺复原与战斗效能分析

LobeChat能否实现AI铸剑师&#xff1f;冷兵器工艺复原与战斗效能分析 在博物馆的展柜前&#xff0c;一位观众轻声问道&#xff1a;“这把唐刀当年是怎么锻造出来的&#xff1f;” 如果这时玻璃中的古剑能“开口”讲述它的淬火温度、折叠次数和战场传奇&#xff0c;那会是怎样一…

作者头像 李华
网站建设 2025/12/25 21:47:57

【完整源码+数据集+部署教程】签名检测系统源码分享[一条龙教学YOLOV8标注好的数据集一键训练_70+全套改进创新点发刊_Web前端展示]

一、背景意义 随着信息技术的迅猛发展&#xff0c;数字化和自动化在各个领域的应用愈加广泛&#xff0c;尤其是在金融、法律和商业等行业中&#xff0c;签名作为一种重要的身份验证手段&#xff0c;其安全性和可靠性受到越来越多的关注。传统的手工签名验证方法不仅耗时耗力&am…

作者头像 李华
网站建设 2026/1/13 16:10:30

【计算机毕业设计案例】基于springBoot茶叶销售管理系统设计与实现基于Java语言的茶叶销售系统的前端设计与实现(程序+文档+讲解+定制)

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…

作者头像 李华