![图片[1]-02.深入解密Java类加载机制——从加载到初始化的完整流程-速优课](http://www.suyouke.com/wp-content/uploads/2026/07/doubao_img_2304x1728_20260722_174942-1-1024x768.png)
本文原创,转载请注明出处。关注博主,持续分享Java进阶知识。
一、开篇:类加载与盖房子的奇妙类比
你知道吗?Java虚拟机加载类的过程,和盖房子有着惊人的相似之处!
盖房子流程:
找建筑师设计方案 → 市政部门审核 → 盖毛坯房 → 装修 → 入住
类加载流程:
加载(查找字节流) → 链接(验证/准备/解析) → 初始化 → 可执行
从class文件到内存中的类,需要经过加载、链接、初始化三大步骤。那么,每个步骤具体都在做什么?是否所有Java类都需要经过这几步呢?
让我们一起揭开类加载的神秘面纱!
二、Java类型体系概览
2.1 类型分类
Java语言的类型分为两大类:
| 类型分类 | 具体类型 | 是否需要加载 |
| 基本类型 | byte/short/char/int/long /float/double/boolean | JVM预先定义,无需加载 |
| 引用类型 | 类、接口 | 需要加载 |
| 数组类 | JVM直接生成 | |
| 泛型参数 | 编译期擦除,不存在于JVM中 |
2.2 字节流的来源
类的字节流可以从多种来源获取:
![图片[2]-02.深入解密Java类加载机制——从加载到初始化的完整流程-速优课](https://www.suyouke.com/wp-content/uploads/2026/07/image-6-1024x685.png)
三、加载阶段:寻找字节流并创建类
3.1 类加载器的层次结构
类加载器采用双亲委派模型,等级森严:
![图片[3]-02.深入解密Java类加载机制——从加载到初始化的完整流程-速优课](https://www.suyouke.com/wp-content/uploads/2026/07/image-5-1024x768.png)
3.2 双亲委派模型详解
工作流程:
- 收到加载请求
- 先转发给父类加载器
- 父类加载器无法加载时,自己尝试加载
核心代码:
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
// 1. 检查是否已加载
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
// 2. 委托父类加载器
if (parent != null) {
c = parent.loadClass(name, false);
} else {
// 3. 父类为null时,使用启动类加载器
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// 父类加载器抛出异常
}
if (c == null) {
// 4. 父类无法加载,自己查找
long t1 = System.nanoTime();
c = findClass(name);
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
3.3 各加载器的职责
| 类加载器 | 负责加载 | 路径来源 |
| 启动类加载器 | JRE核心类(java.lang.*等) | JRE/lib目录 |
| 平台类加载器 | Java SE扩展模块 | JRE/lib/ext目录 |
| 应用类加载器 | 用户应用程序类 | -cp/-classpath指定 |
| 自定义类加载器 | 按需加载 | 自定义路径 |
3.4 类的唯一性
类的唯一标识 = 类加载器实例 + 类的全名
ClassLoader cl1 = new CustomClassLoader();
ClassLoader cl2 = new CustomClassLoader();
Class<?> c1 = cl1.loadClass("com.example.MyClass");
Class<?> c2 = cl2.loadClass("com.example.MyClass");
System.out.println(c1 == c2); // false!两个不同的类
这一特性被广泛应用于:
- 热部署/热更新
- 运行同一类的不同版本
- OSGi模块化框架
四、链接阶段:整合到JVM中
链接分为三个阶段:验证、准备、解析
4.1 验证阶段:确保合规性
验证内容:
- 文件格式验证(魔数、版本号等)
- 元数据验证(类结构、继承关系等)
- 字节码验证(指令合法性、类型安全等)
- 符号引用验证(引用目标存在性等)
类比: 盖房子前的市政审核
4.2 准备阶段:分配内存
主要任务:
- 为静态字段分配内存
- 设置默认初始值(0/false/null)
- 构造方法表等数据结构
注意: 此时尚未执行Java代码中的初始化赋值!
public class Example {
static int a = 10; // 准备阶段:a = 0(默认值)
static String b; // 准备阶段:b = null
}
4.3 解析阶段:符号引用转实际引用
符号引用 vs 实际引用:
| 类型 | 说明 | 示例 |
| 符号引用 | 字符串描述,编译期生成 | “java.lang.String” |
| 实际引用 | 内存地址或偏移量,运行期解析 | 指向String类的指针 |
解析时机: JVM规范不要求在链接阶段完成解析,只需在执行相关字节码前完成即可。
五、初始化阶段:赋予生命
5.1 初始化的内容
两种初始化方式:
public class InitializationDemo {
// 方式1:声明时直接赋值
static int a = 10;
// 方式2:静态代码块
static {
a = 20;
System.out.println("Static block");
}
// final + 基本类型/字符串:编译期常量
static final int CONST = 100; // 直接存入常量池
}
特殊处理:
- final + 基本类型/字符串:编译期标记为ConstantValue,由JVM直接赋值
- 其他静态字段赋值和静态代码块:合并到方法中
5.2 初始化触发时机
JVM规范枚举了8种触发情况:
| 序号 | 触发场景 | 示例 |
| 1 | 启动时初始化主类 | java Main |
| 2 | 新建类实例 | new MyClass() |
| 3 | 调用静态方法 | MyClass.staticMethod() |
| 4 | 访问静态字段 | MyClass.staticField |
| 5 | 子类初始化触发父类 | new Child() → 先初始化Parent |
| 6 | 接口default方法触发 | 实现类初始化触发接口 |
| 7 | 反射调用 | Class.forName(“MyClass”) |
| 8 | 调用MethodHandle | 首次调用时初始化 |
5.3 经典应用:延迟初始化单例
public class Singleton {
private Singleton() {}
// 静态内部类
private static class LazyHolder {
static final Singleton INSTANCE = new Singleton();
static {
System.out.println("LazyHolder.<clinit>");
}
}
public static Singleton getInstance() {
return LazyHolder.INSTANCE; // 触发LazyHolder初始化
}
}
原理:
- LazyHolder类在首次访问时才会被初始化
- 类初始化是线程安全的(JVM保证方法只执行一次)
- 实现了真正的延迟加载
六、动手实践:验证类加载过程
6.1 实验代码
public class Singleton {
private Singleton() {}
private static class LazyHolder {
static final Singleton INSTANCE = new Singleton();
static {
System.out.println("LazyHolder.<clinit>");
}
}
public static Object getInstance(boolean flag) {
if (flag) return new LazyHolder[2]; // 新建数组
return LazyHolder.INSTANCE; // 访问静态字段
}
public static void main(String[] args) {
getInstance(true); // 新建LazyHolder数组
System.out.println("----");
getInstance(false); // 访问LazyHolder.INSTANCE
}
}
6.2 运行命令
# 编译
$ javac Singleton.java
# 运行并打印类加载信息
$ java -verbose:class Singleton
6.3 思考问题
- 新建数组会导致LazyHolder的加载吗?
- 新建数组会导致LazyHolder的初始化吗?
- 为什么访问LazyHolder.INSTANCE才会触发初始化?
提示: JVM规范中,创建数组类并不会触发其元素类型的初始化!
七、总结
核心要点回顾
- 三大步骤:加载 → 链接(验证/准备/解析) → 初始化
- 类加载器:双亲委派模型,启动类加载器→平台类加载器→应用类加载器→自定义类加载器
- 类唯一性:类加载器实例 + 类全名共同确定
- 初始化时机:8种触发情况,类初始化线程安全
- 延迟初始化:利用静态内部类实现高效线程安全的单例
关键理解
- 基本类型由JVM预先定义,无需加载
- 数组类由JVM直接生成,无需加载字节流
- 解析阶段可以延迟到执行相关字节码前
- final+基本类型/字符串的静态字段在编译期就已确定
写在最后
类加载机制是JVM的核心功能之一,深入理解它不仅能帮助我们写出更高效的代码,还能为学习反射、动态代理、热部署等高级特性打下坚实基础。
如果觉得本文对你有帮助,欢迎点赞、收藏、关注!后续我会继续分享更多JVM相关的深度解析文章。
📚 关注我,一起深入探索Java虚拟机的奥秘!








请登录后查看评论内容