Java类加载过程分为加载、验证、准备、解析、初始化五阶段,其中静态代码块在初始化阶段执行且仅一次,由<clinit>方法按源码顺序合并执行,触发条件为首次主动使用该类。

Java 类加载过程不是“一上来就跑 main”,而是按固定阶段逐步推进,静态代码块就在其中最关键的初始化阶段执行——它只运行一次、早于任何对象创建、也早于 main 方法。
类加载五阶段:从字节码到可运行
一个类从 .class 文件变成 JVM 里能用的类型,必须经过:
- 加载:按全限定名找到字节码,读入内存,在方法区生成运行时结构,并创建 Class 对象
- 验证:检查字节码是否合规(魔数、版本、指令逻辑等),防止破坏虚拟机
- 准备:为 static 变量分配内存并设默认值(如 int→0,Object→null);但 public static final int PORT = 8080; 这类编译期常量会直接赋值
- 解析:把常量池里的符号引用(比如 “Math.PI” 字符串)替换成真实内存地址
- 初始化:执行 <clinit>() 方法——这是编译器自动生成的静态初始化器,合并所有静态变量赋值语句和静态代码块,按源码顺序执行
静态代码块到底什么时候执行?
它只在类的 初始化阶段 执行,且仅一次。触发条件是“首次主动使用”,包括:
- new 该类的实例
- 访问该类声明的静态字段(非 final 编译期常量)
- 调用该类的静态方法
- 反射获取该类的 Class 对象(如 Class.forName("X"))
- 子类初始化时,父类尚未初始化,则先触发父类初始化
- JVM 启动时,把 main 方法所在类作为首个要初始化的类
注意:通过子类引用父类的静态字段(如 SubClass.value),只触发父类初始化,不触发子类初始化。
立即学习“Java免费学习笔记(深入)”;
静态成员执行顺序:从上到下、先父后子
同一类中,静态变量显式赋值和静态代码块严格按源码书写顺序执行;有继承时,顺序是:
- 父类静态代码块 → 父类静态变量赋值
- 子类静态代码块 → 子类静态变量赋值
例如:
class Parent {
static { System.out.println("Parent init"); }
static String p = "p";
}
class Child extends Parent {
static { System.out.println("Child init"); }
static String c = "c";
}
首次 new Child() 或调用 Child.someStaticMethod(),输出一定是:
Parent init → p 赋值 → Child init → c 赋值
为什么静态代码块适合做环境准备?
因为它在类加载完成初始化时自动运行,无需实例、线程安全、只一次,适合:
- 注册 JDBC 驱动(Class.forName("com.mysql.cj.jdbc.Driver"))
- 加载配置文件或初始化连接池
- 挂载全局工具类或常量映射表
但要注意避开陷阱:不能抛未捕获异常(会导致类初始化失败,后续所有使用都报 NoClassDefFoundError)、避免强依赖外部服务、别做耗时操作(会阻塞整个类加载)。


















