Java类初始化仅在六种主动引用场景下触发:new实例、访问/修改非final静态字段、调用静态方法、反射Class.forName()、子类初始化时父类未初始化、虚拟机启动主类或MethodHandle调用静态成员。

Java 中类初始化不是随加载自动发生的,而是严格按规范在特定“主动引用”场景下才触发。真正起作用的是 JVM 字节码层面的约束,不是表面语法。下面这六种情况,只要首次发生且目标类尚未初始化,JVM 就必须执行其 <clinit> 方法(即静态变量赋值 + 静态代码块)。
1. new 实例化对象
执行 new X() 时,JVM 必须确保 X 已完成初始化——因为构造器可能依赖静态字段或 static 块的执行结果。
-
new X[10]是创建数组对象,不触发 X 初始化 - 即使 X 没写
static{},只要定义了非 final 静态变量(如static int a;),new X()仍会触发其初始化(赋予默认值 0 并执行<clinit>) - 初始化只发生一次,后续
new X()不重复触发
2. 访问或修改非 final 静态字段
读取(getstatic)或赋值(putstatic)静态字段,表明程序已明确依赖该类的静态状态。
- 例外:
public static final int X = 123;或public static final String S = "abc";这类编译期常量,值被内联到调用方字节码中,不产生符号引用,不触发初始化 - 子类引用父类静态字段(如
Child.value,value 定义在 Parent 中),只触发 Parent 初始化,Child 不动
3. 调用静态方法
执行 invokestatic 指令,无论方法体是否为空、是否实际用到静态成员,只要调用就触发初始化。
你接手一个需求时,是不是经常这样:"改一个小字段 → 结果把不相关的代码也改了"、"需求说得很模糊 → 做着做着发现跑偏了"、"数据库加个字段 → 上线才发现漏了数据兼容"、"做了个新功能 → 一个月后自己都忘了当时怎么设计的"、"写到一半被中断 → 再回来不知道从哪继续";这个 Skill 就是专门解决这些问题的。 它会自动探测你的项目环境(JDK、Spring Boot、ORM、数据库、前端框架),记在
立即学习“Java免费学习笔记(深入)”;
- 包括
MyClass.doNothing()、Integer.valueOf(1)等所有静态方法调用 - 接口 default 方法不在此列;但若其实现类被初始化,接口本身可能因第6条规则被顺带初始化
4. 反射加载并使用类
Class.forName("X") 默认触发初始化(等价于 forName("X", true, loader));而 ClassLoader.loadClass("X") 仅加载不初始化。
- 反射访问字段、方法、构造器时,若目标类未初始化,JVM 会自动补上初始化步骤
-
Class.forName("X", false, loader)显式禁用初始化,适用于纯元编程场景(如仅需获取泛型信息) - Spring 的
@Bean方法中通过反射获取类型,常成为隐式初始化源头
5. 子类初始化时父类未就绪
当 JVM 开始初始化子类,发现其直接父类尚未初始化,则强制先完成父类初始化(递归向上,直到 Object)。
- 仅限类继承关系;接口继承不要求父接口提前初始化,除非真正用到父接口的静态成员
- 子类初始化本身由上述任一场景(如 new、静态调用等)触发,父类初始化是连带动作
6. 启动主类或动态语言支持调用
虚拟机启动时,用户指定的含 main(String[]) 方法的类,必定首个被初始化。
- JDK 7+ 的
MethodHandle若解析出REF_getStatic、REF_putStatic或REF_invokeStatic,且对应类未初始化,则立即触发 - 这是较隐蔽的触发点,常见于 Lambda 表达式、invokedynamic 指令底层实现中

















