jvm类加载过程严格按加载、验证、准备、解析、初始化五阶段顺序启动,但执行非完全线性,解析可能延迟至初始化后;加载将字节码载入内存并创建class对象;验证确保字节码安全;准备为静态变量设默认值;解析将符号引用转为直接引用;初始化执行方法。

JVM 类加载过程的五个阶段按固定顺序启动,但执行并非完全线性,部分环节会交叉或延迟——尤其是解析阶段可能推迟到初始化之后。核心是:加载、验证、准备、解析、初始化这五步构成完整类加载流程,后续才是使用和卸载。
加载:字节码进内存,Class对象诞生
这是入口阶段,由类加载器(如Bootstrap、Extension、Application)完成。它不执行任何Java代码,只做三件事:
- 根据全限定名(如java.util.ArrayList)定位并读取.class字节码流,来源可以是本地文件、JAR包、网络甚至运行时动态生成(如CGLIB代理);
- 将字节码转换为方法区中的运行时数据结构(含字段、方法、常量池等元信息);
- 在堆中创建一个java.lang.Class实例,作为该类在JVM中的唯一“镜像”和访问入口。
验证:确保字节码安全合规
加载完成后立即开始,目的是防止恶意或错误字节码危害JVM。它分四层检查:
- 文件格式验证:比如魔数是否为0xCAFEBABE、主次版本号是否支持;
- 元数据验证:检查类是否有父类、是否继承final类、是否实现抽象方法等;
- 字节码验证:分析操作码流,确保跳转不会越界、类型匹配、控制流安全;
- 符号引用验证:确认常量池中引用的类、字段、方法确实存在且可访问。
任一失败都会抛出VerifyError;可通过-Xverify:none跳过,但生产环境不建议。
你接手一个需求时,是不是经常这样:"改一个小字段 → 结果把不相关的代码也改了"、"需求说得很模糊 → 做着做着发现跑偏了"、"数据库加个字段 → 上线才发现漏了数据兼容"、"做了个新功能 → 一个月后自己都忘了当时怎么设计的"、"写到一半被中断 → 再回来不知道从哪继续";这个 Skill 就是专门解决这些问题的。 它会自动探测你的项目环境(JDK、Spring Boot、ORM、数据库、前端框架),记在
准备:静态变量分配内存并设默认值
为类变量(static修饰的变量)在方法区分配内存,并设置初始值(注意:不是代码里写的值,而是数据类型的零值):
- int → 0,long → 0L,boolean → false,reference → null;
- 例外:被static final修饰的**编译期常量**(如public static final int PORT = 8080;),会在准备阶段直接赋值为8080,因为其值已写入常量池的ConstantValue属性。
解析:把符号引用转成真实地址
将常量池中的符号引用(如类名、方法名、字段名等字符串描述)解析为直接引用(内存地址、偏移量等JVM可直接使用的指针)。例如:
- 把CONSTANT_Methodref_info指向的方法名+描述符,解析为该方法在类方法表中的索引或具体入口地址;
- 把接口方法调用(invokeinterface)延迟到运行时才解析,支持多态;
- invokedynamic指令的解析更是完全推迟到首次执行时(通过CallSite机制),这是Lambda和动态语言支持的基础。
初始化:执行 方法
这是真正运行Java代码的起点。JVM会收集所有static变量赋值语句和static{}块,按源码顺序合并为一个特殊方法
- 父类
先于子类执行; - 接口不需要初始化(除非有static字段且被主动引用);
- 同一个类加载器下,
只会被执行一次; - 触发时机包括:new对象、调用静态方法、访问静态字段(非编译期常量)、反射Class.forName()、初始化子类时父类未初始化等。










