news 2026/6/10 22:36:18

更深入的了解类加载机制与类加载器

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
更深入的了解类加载机制与类加载器

一、前言:从.java到程序运行

Java 是半编译半解释型语言,代码执行分为两大阶段:

  1. 编译阶段:通过javac编译器将.java源代码编译为平台无关的.class字节码文件
  2. 运行阶段:JVM 读取.class字节码,经过类加载机制将类信息载入内存,最终由解释器 + JIT 即时编译器执行。

类加载机制就是把.class文件中的二进制数据,加载到 JVM 内存、校验、处理并初始化的全过程,是 Java 程序运行的基石。


二、类加载完整生命周期(五大阶段)

类加载生命周期固定分为加载、验证、准备、解析、初始化五个阶段。 其中验证、准备、解析统称为连接阶段;加载、初始化阶段由代码主动触发,其余阶段由 JVM 自动执行。

1. 加载阶段(Loading)

类加载的第一步,核心工作:

  1. 根据类的全限定名,读取外部.class二进制字节流(各种来源:本地文件、网络、动态生成、Jar 包等);
  2. 将字节流转换为方法区的运行时数据结构;
  3. 在 Java 堆中创建对应java.lang.Class对象,作为访问该类的唯一入口。

补充:加载阶段没有强制时机,JVM 规范允许灵活加载类。

2. 验证阶段(Verification)

连接阶段第一步,安全校验,保证字节码符合 JVM 规范、无恶意代码:

  • 文件格式验证:校验字节流是否为合法.class格式;
  • 元数据验证:校验类结构、继承、语法规则是否合法;
  • 字节码验证:校验方法体指令逻辑,防止非法操作;
  • 符号引用验证:校验常量池中符号引用的合法性。

3. 准备阶段(Preparation)

类静态变量在方法区分配内存,并赋予默认零值(不是代码中设置的初始值)。

  • 普通成员变量:随对象创建分配内存,和本阶段无关;
  • static变量:分配内存并赋 JVM 默认零值;
  • static final常量:编译期就确定值,准备阶段直接赋值为定义值。

示例:

public static int num = 10;

准备阶段:num = 0(int 类型零值); 等到初始化阶段,才会赋值为10

在这里需要为大家纠正一个错误说法:在JDK8之后,元空间替代了方法区

这种说法是错误的,首先元空间和永久代都是方法区的具体实现

方法区是一个抽象的概念,在JDK8之前,类的元信息、常量池、静态变量等信息存储在方法区的具体实现----永久代中,当JDK8出现之后,就变成了类的元信息等存储在方法区的具体实现----元空间中,而原来在永久代中的常量池、静态变量就转移到了堆中,因此不能说元空间替代了方法区,也不能说元空间替代了永久代;详见图:

4. 解析阶段(Resolution)

核心作用:将运行时常量池中的符号引用替换为直接引用(真实内存地址),也就是我们常说的代码连接

  • 符号引用:编译期生成,只记录类名、方法名、字段名、描述符等字符串信息,不包含真实内存地址;
  • 直接引用:指向内存的指针 / 偏移量,可以直接定位到目标类、字段、方法。

解析范围:类、接口、字段、普通方法。

特点:支持动态连接,允许运行时第一次使用时再完成解析,并非必须在初始化前执行。

5. 初始化阶段(Initialization)

类加载最后一步,也是真正执行代码逻辑的阶段:

  1. 主动执行静态代码块、静态变量显式赋值,按代码书写顺序自上而下执行
  2. 仅当类被主动使用时才会触发(new 对象、调用静态方法 / 静态变量、反射、子类初始化等);
  3. JVM 保证多线程环境下,类初始化只会执行一次。

三、JVM 四大类加载器体系

类加载器是执行 “加载阶段” 的核心组件,负责读取.class字节流并载入内存。主流的HotSpot JVM 分为四层加载器,自上而下层级划分。

1. 引导类加载器(Bootstrap ClassLoader)

  • 由 C/C++ 语言实现,JVM 内置,Java 代码无法直接获取;
  • 负责加载 JDK 核心类库:JAVA_HOME/jre/lib下核心 Jar(如rt.jarjava.lang.*java.util.*)。

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

  • Java 语言实现;
  • 负责加载JAVA_HOME/jre/lib/ext目录下的扩展类库。

3. 应用类加载器(Application ClassLoader / 系统类加载器)

  • Java 语言实现,是开发者自定义类的默认加载器
  • 负责加载项目classpath路径下的所有用户编写的类、第三方框架类。

4. 自定义类加载器

  • 开发者继承ClassLoader重写方法实现;
  • 适用场景:热部署、代码加密、从网络 / 自定义路径加载类。

补充:命名空间规则

类全限定名 + 加载它的类加载器,共同决定一个类在 JVM 中的唯一性。 同一个.class文件,被不同类加载器加载后,会被 JVM 判定为两个完全不同的类,无法相互赋值。


四、双亲委派模型(类加载核心规则)

1.核心定义

双亲委派是 JVM 默认的类加载协作机制:当一个类加载器收到加载请求时,优先向上委派给父加载器处理;只有父加载器无法完成加载,当前加载器才会尝试自己加载。

注意:这里的 “父子” 是逻辑上下级,并非 Java 类继承关系。

2.执行流程

以加载一个用户自定义类为例:

  1. 应用类加载器收到加载请求 → 委派给扩展类加载器
  2. 扩展类加载器收到请求 → 委派给引导类加载器
  3. 引导类加载器检查:非核心类,无法加载 → 回传给扩展类加载器;
  4. 扩展类加载器检查:非扩展类,无法加载 → 回传给应用类加载器;
  5. 应用类加载器在classpath中查找并加载目标类;
  6. 全部父加载器都无法加载,抛出ClassNotFoundException

3. 两大核心作用

  1. 安全防护:防止恶意代码篡改 JDK 核心类。例如用户自定义java.lang.String,会被引导类加载器优先加载原生核心类,自定义类永远不会生效;
  2. 避免重复加载:同一个类只会被上层加载器加载一次,全局复用,节约内存。

五、双亲委派模型的三次破坏场景

双亲委派是默认规范,但为了满足功能灵活性,历史上出现三次典型 “破坏” 场景,也是面试高频考点。

1. JDK 1.2 之前(历史原因)

早期ClassLoader的核心方法loadClass()由开发者重写,而双亲委派逻辑就写在该方法中。开发者重写后极易打破委派规则,JDK1.2 后规范了双亲委派,并推荐重写findClass()而非loadClass()

2. SPI 服务提供者接口(最常用)

典型案例:JDBC 数据库驱动

  • 数据库接口(java.sql.Driver)由引导类加载器加载;
  • 第三方驱动实现类(MySQL、Druid 等)由应用类加载器加载;
  • 引导类加载器无法向下加载应用层类,因此通过线程上下文类加载器反向委派,打破双亲委派。

3. JDK 9 模块化系统(JPMS)

JDK9 引入模块机制,取消传统三层加载器结构,每个模块拥有独立加载器,模块之间隔离访问,彻底改变了原有的双亲委派逻辑。


结尾

类加载机制是 JVM 体系的重中之重,不仅用来排查ClassNotFoundExceptionNoClassDefFoundError等线上问题,也是学习动态代理、热部署、容器框架的底层基础。结合类加载器、双亲委派整套体系理解,才能真正吃透 Java 底层运行逻辑。

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

鸿蒙从零掌握核心:幸运数字生成器实战

一、引言:为什么选择这个例子?在 HarmonyOS 应用开发的学习路径中,开发者面临的第一道坎往往不是复杂的业务逻辑,而是理解新框架的表达范式。"幸运数字生成器" 虽然功能简单——两个滑块设置范围、一个按钮抽取随机数、…

作者头像 李华
网站建设 2026/6/10 22:34:22

LeetCode 239.滑动窗口最大值

给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。 返回 滑动窗口中的最大值 。 示例 1: 输入:nums [1,3,-1,-3,5,3,6,7], k 3 输出…

作者头像 李华
网站建设 2026/6/10 22:32:25

金刚石NV色心量子传感技术:从原理到应用的前沿探索

金刚石NV色心量子传感技术的应用前景金刚石中的氮-空位(NV)色心是近年来量子传感领域的研究热点。这种原子尺度的缺陷具有独特的量子特性——其自旋态可通过激光和微波精确操控,对外界磁场、温度和电场极为敏感。基于此原理的量子传感器在室温…

作者头像 李华
网站建设 2026/6/10 22:26:17

leecodecode【状态机DP】【2026.6.9打卡-java版本】

买卖股票的最佳时机 要点:只能买一次,维护min,和 ans class Solution {public int maxProfit(int[] prices) {int min Integer.MAX_VALUE;int ans 0;for(int price : prices){min Math.min(min, price);ans Math.max(ans, price - min)…

作者头像 李华
网站建设 2026/6/10 22:21:12

用原生JS手搓一个Flappy Bird小游戏(完整代码+逐行讲解)

用原生JS手搓一个Flappy Bird小游戏(完整代码逐行讲解)最近有学员问我:"学JavaScript到底有什么用?"我的回答是——它能让你亲手创造世界。今天我们就用不到200行代码,还原那个曾经风靡全球的Flappy Bird。不…

作者头像 李华