Java类加载过程为加载→验证→准备→解析→初始化五阶段,其中初始化是执行静态代码和静态赋值的起点,仅在主动引用时触发。

Java程序启动时,JVM并不是一上来就执行main方法,而是先完成类加载与初始化的一整套流程。这个过程直接决定了静态变量、静态代码块、普通代码块和构造器的执行顺序——理解它,才能真正看懂Java程序“从哪开始、怎么动起来”。
类加载的五个关键阶段
一个类从磁盘上的.class文件变成可运行的对象,要经历:加载 → 验证 → 准备 → 解析 → 初始化。其中前四步统称“连接”,而初始化才是真正执行用户代码的起点。
-
加载:JVM根据全限定名(如com.example.Main)找到.class字节码,读入内存,在方法区构建运行时结构,并在堆中创建对应的
Class对象 - 验证:检查字节码是否合规(魔数、版本、语义、指令逻辑等),防止恶意或损坏的类破坏虚拟机
-
准备:为
static变量分配内存并设默认值(如int→0,Object→null),但final static常量会在此阶段直接赋值(如public static final int PORT = 8080;) - 解析:把常量池里的符号引用(比如类名、方法名字符串)替换成内存中的真实地址(直接引用)
-
初始化:执行
<clinit>()方法——这是编译器自动生成的静态初始化器,合并了所有静态变量赋值语句和静态代码块,按源码书写顺序执行
静态代码块和静态变量的执行时机
它们只在类的初始化阶段执行,且仅一次。触发初始化的“主动引用”包括:new实例、访问静态字段(非final常量)、调用静态方法、反射获取Class、子类初始化时父类先行初始化、以及主类被JVM选为入口。
你接手一个需求时,是不是经常这样:"改一个小字段 → 结果把不相关的代码也改了"、"需求说得很模糊 → 做着做着发现跑偏了"、"数据库加个字段 → 上线才发现漏了数据兼容"、"做了个新功能 → 一个月后自己都忘了当时怎么设计的"、"写到一半被中断 → 再回来不知道从哪继续";这个 Skill 就是专门解决这些问题的。 它会自动探测你的项目环境(JDK、Spring Boot、ORM、数据库、前端框架),记在
- 静态变量声明与静态代码块按源码自上而下执行,谁在前面谁先跑
- 静态变量的显式初始化(如
static String s = init();)是在初始化阶段调用方法,不是准备阶段 - 访问
final static基本类型或字符串字面量(如Math.PI)不会触发类初始化,因为值已在编译期确定并内联
继承体系下的完整执行顺序
当创建子类实例(如new Child())时,整个链条被拉起:
立即学习“Java免费学习笔记(深入)”;
- 先触发父类初始化(若未初始化):父类静态块 → 父类静态变量赋值 → 子类静态块 → 子类静态变量赋值
- 再进入实例创建:父类普通代码块 → 父类构造器 → 子类普通代码块 → 子类构造器
- 注意:普通代码块(即
{...})属于实例级别,每次new都会执行;静态代码块只在类加载完成初始化时跑一次
main方法作为入口的特殊性
JVM启动时,会把命令行指定的主类(含public static void main(String[])的那个类)作为第一个要初始化的类。也就是说,main方法所在的类,其静态部分(静态变量、静态块)会在程序真正开始前全部执行完毕。
- 如果
main里第一行是System.out.println(Child.n);,那Child类会立即初始化(包括它的父类) - 如果
main里只写new Parent().doSomething();,那么Parent类初始化,Child类不会被加载(除非后续代码触发) - 类加载本身不等于初始化;只有满足“主动引用”条件时,才会走到初始化这一步

















