news 2026/4/15 6:25:27

深入理解类加载器

作者头像

张小明

前端开发工程师

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/4/10 18:20:00

如何了解腾讯云国际站代理商的NLP有什么优势呢?

要了解腾讯云国际站代理商的 NLP 优势&#xff0c;可从腾讯云国际站 NLP 本身的技术能力&#xff0c;以及代理商提供的附加服务两方面切入&#xff0c;也能通过官方及代理商渠道进一步核实&#xff0c;具体如下&#xff1a;产品本身的核心技术优势多语言与高准确率兼具&#xf…

作者头像 李华
网站建设 2026/4/13 9:48:24

腾讯云国际站代理商的TAPD适合哪些类型的企业?

腾讯云国际站代理商的 TAPD 凭借敏捷研发管理、跨地域协作、多生态集成及灵活付费与定制化服务等优势&#xff0c;适配多个行业、不同规模且有跨境协作或合规需求的企业&#xff0c;具体类型如下&#xff1a;跨境互联网与游戏企业这类企业常面临多地区工作室协作、版本快速迭代…

作者头像 李华
网站建设 2026/4/14 15:06:14

钢铁厂除氧供气 / 炉门驱动用工业级螺杆空压机​选型注意

一、明确工况核心需求&#xff0c;锚定选型基准 除氧供气工况特性&#xff1a;需为锅炉给水除氧系统提供干燥、洁净的压缩空气&#xff0c;核心要求是无油洁净度&#xff08;避免油污染影响水质和锅炉安全&#xff09;、压力稳定&#xff08;除氧过程需恒定压力保障除氧效率&am…

作者头像 李华
网站建设 2026/4/12 18:54:31

Web应用安全测试指南

在数字化时代&#xff0c;Web应用已成为企业和用户交互的核心渠道&#xff0c;然而随之而来的安全威胁日益严峻。作为软件测试从业者&#xff0c;掌握系统的安全测试方法不仅是职责所在&#xff0c;更是保障应用可靠性和用户信任的关键。本指南旨在为测试人员提供一套实用、可操…

作者头像 李华
网站建设 2026/4/14 15:41:47

身价暴跌!28 岁球员从 5500 万到 200 万

他曾是阿贾克斯青春风暴中最耀眼的核心之一&#xff0c;是金球奖候选名单上的未来之星&#xff1b;如今&#xff0c;28岁——这本该是一名中场球员的黄金年龄——他的身价却仅剩200万欧元&#xff0c;不及巅峰时的零头。从众星捧月到无人问津&#xff0c;唐尼范德贝克的故事&am…

作者头像 李华
网站建设 2026/4/14 4:34:09

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

一、背景意义 随着人工智能技术的迅猛发展&#xff0c;计算机视觉领域的研究日益受到关注&#xff0c;尤其是人脸检测技术在安全监控、智能家居、社交媒体等多个应用场景中发挥着越来越重要的作用。人脸检测作为计算机视觉中的一个关键任务&#xff0c;旨在从图像或视频中自动…

作者头像 李华