被动引用不会触发类初始化,本质是jvm规范定义的优化行为:仅完成符号解析与链接,跳过执行;包括编译期常量访问、子类引用父类非final静态字段、创建类数组及获取class对象四种典型场景。

被动引用不会触发类初始化,本质是 JVM 对“使用类”做了精细划分:只有主动使用才会执行 <clinit></clinit>(静态初始化块和静态变量赋值),而被动引用仅完成符号解析,跳过初始化阶段。它不是 bug,而是 JVM 规范明确规定的优化行为,目的是延迟加载、减少开销。
编译期常量访问:值被“抄走”,类被绕过
当字段满足 static final + 基本类型或 String + 编译期可确定值 时,JVM 在编译阶段就把该值直接写入调用方的常量池,运行时完全不接触原类。
-
会绕过初始化的写法:比如
public static final int PORT = 8080;,其他类读取Config.PORT,Config类甚至不会被加载 -
会触发初始化的写法:如
static final Integer PORT = 8080;(包装类型)、static final String MSG = new String("ok");(运行期创建)、static final int CODE = calc();(需调用方法)——这些都不是编译期常量,访问即触发初始化
子类引用父类静态字段:只动父类链接,不动父类初始化
子类代码中直接使用父类声明的静态字段(非 final 或非常量),JVM 只会确保父类完成加载、验证、准备、解析(即链接阶段),但不会执行其 <clinit></clinit>,前提是该字段未在子类的静态上下文中被首次读写。
- 例如:
Parent.count是protected static int count = 10;,子类仅System.out.println(Parent.count);—— 父类可能不初始化 - 但若子类静态块里写
Parent.count++;,这就构成“主动使用”,父类立即初始化 - 注意:这种引用 不会导致子类自身初始化,子类的
static块和静态字段仍保持未执行状态
数组创建与 Class 对象获取:类名出现 ≠ 类被用
仅因语法需要提及类名,但未真正访问其运行时结构,就属于被动引用。
-
MyClass[] arr = new MyClass[5];—— 只加载MyClass的数组类(如[LMyClass;),MyClass本体不初始化 -
Class.forName("MyClass", false, loader)或ClassLoader.loadClass("MyClass")—— 第二个参数为false时,只加载不初始化 -
MyClass.class—— 获取 Class 对象,不触发初始化(这是最常被误认为会初始化的操作)
核心判断口诀:看有没有“第一次主动读写”
是否初始化,不看代码里写了什么类名,而看是否对类的静态成员做了首次实质性访问:
- 首次读取一个非编译期常量的
static字段(且该字段所在类尚未初始化)→ 主动使用 → 初始化 - 调用
static方法 → 主动使用 → 初始化 - 新建该类实例(
new MyClass())→ 主动使用 → 初始化 - 反射调用非 public 成员、或通过
Class.forName("X", true, cl)显式要求初始化 → 主动使用 - 其余情形,包括上面三类典型被动引用,都不走
<clinit></clinit>










