Java类加载过程分五步渐进构建:加载(读字节码、生成Class对象)、验证(校验安全性)、准备(分配static内存并设默认值)、解析(符号引用转直接引用)、初始化(执行方法),各阶段职责、触发时机与干预点均不同。

Java 类加载过程不是一次性把字节码“塞进内存”就完事,而是分五步渐进构建信任:每一步干的事不同、触发时机不同、你能观测或干预的地方也不同。
加载:把字节码读进来,生成 Class 对象
这是整个流程的起点,但不等于“马上能用”。ClassLoader 负责从文件系统、JAR、网络甚至动态生成的字节码中定位并读取 .class 文件,将其转化为 JVM 内部的数据结构,并在方法区创建一个 java.lang.Class 实例作为访问入口。
- 不强制立即执行——JVM 按需加载,比如只声明类型(
MyClass x;)不会触发加载 -
ClassLoader.loadClass("X")只走到加载+解析前,不初始化;Class.forName("X")默认会初始化 - 加
-verbose:class参数启动 JVM,所有真正完成加载的类名都会打印出来 -
MyClass.class.getResource("/MyClass.class")返回非 null URL,说明字节码已定位并读入内存
验证:确保字节码安全合规
防止恶意或损坏的 class 文件破坏 JVM 运行时。它是一系列校验,贯穿早期阶段,包括:
- 文件格式验证:检查魔数(0xCAFEBABE)、主次版本号、常量池结构等是否合法
- 元数据验证:确认类是否有非法继承(如 final 类被继承)、是否违反访问控制
- 字节码验证:分析方法体控制流,确保操作数栈不会溢出、跳转不会越界、类型使用正确
- 符号引用验证:检查常量池里引用的类、字段、方法是否存在且可访问
准备:给 static 变量分配内存,设默认值
为类变量(static 修饰的字段)在方法区分配内存空间,并设置成对应类型的零值(int→0、boolean→false、Object→null),不执行任何赋值语句或静态块。
当用户要求为 Java 代码添加注释、修改注释、审查注释、整理注释格式,或要求按照代码注释规范处理 Java 文件时,使用本技能。本技能用于统一生成和维护 Java 类注释模板、按 Git 对比 release 判断版本号、补充每行代码上方中文块注释、调整注释空行规则,并保持 Java 文件 UTF-8 无 BOM 编码。典型触发场景包括:补充类注释、更新 @version、检查 @author 来源、整理行尾注释、把注释放到代码上一行、检查注释是否符合规范、构建前检查 Java 注释风格。
立即学习“Java免费学习笔记(深入)”;
-
static int count = 10;→ 准备阶段count = 0,初始化阶段才变成 10 -
static final int X = 123;(编译期常量)→ 准备阶段直接设为 123,跳过初始化赋值 -
static final String Y = new String("hello");→ 不是编译期常量,必须等到初始化阶段才赋值
解析:把符号引用换成真实地址
把常量池里的字符串形式引用(如 "java/lang/String" 或 "com/example/Service.doWork()V")替换成 JVM 内存中真实的地址指针,也就是“直接引用”。
- 解析对象包括:类/接口、字段、普通方法、接口方法
- 多数解析发生在初始化前,但某些场景(如 invokedynamic)允许延迟到首次使用时再解析
- 能否成功解析,取决于目标类是否已加载、是否可访问、签名是否匹配
初始化:<clinit></clinit> 方法被执行,真正“活”起来
这是类第一次真正开始运行 Java 代码的阶段。JVM 自动合成一个 <clinit></clinit> 方法,包含所有 static 变量的赋值语句和 static{} 块中的逻辑,并保证线程安全(同一时间只有一个线程能执行它)。
- 触发条件有六种主动引用,例如:
new实例化、访问static字段(非 final)、调用static方法、反射、启动主类、使用 MethodHandle 等 - 父类优先原则:若子类初始化,先确保其父类已完成初始化
- 如果
static{ System.out.println("init"); }打印了,说明初始化确实发生了——这不会出现在-verbose:class日志里,只会出现在控制台

















