被动引用不触发类初始化,本质是JVM规范将“使用类”分为主动与被动:主动使用(如读写非编译期常量静态字段、调用静态方法等)才执行<clinit>,被动引用(如数组创建、Class.forName(false)、.class获取)仅完成符号解析与链接,跳过初始化。

被动引用不触发类初始化,本质是 JVM 规范对“使用类”做了明确分层:只有主动使用才执行 <clinit>(静态初始化块和静态变量赋值),而被动引用仅完成符号解析与链接阶段,跳过初始化逻辑。这不是 bug,而是标准规定的延迟加载机制,目的是减少启动开销、避免无谓初始化。
编译期常量被“抄走”,原类彻底绕过
当字段满足 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 静态字段(如 Parent.count),JVM 只确保父类完成加载、验证、准备、解析(即链接),但不执行其 <clinit>。前提是该字段未在子类的静态上下文中被首次读写。
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
-
不触发父类初始化:
System.out.println(Parent.count);,且父类尚未初始化、子类也无静态块操作该字段 -
触发父类初始化:子类静态块中出现
Parent.count++或System.out.println(Parent.count);→ 构成“主动使用”,父类立即初始化 - 子类自身仍不初始化:即使父类初始化了,子类的静态块和静态变量依然保持未执行状态
数组创建和 Class 对象获取,只是“提名字”
仅因语法需要提及类名,但未真正访问其运行时结构,就属于被动引用。JVM 认为这不构成“使用”,只做类型检查或加载数组类。
立即学习“Java免费学习笔记(深入)”;
-
MyClass[] arr = new MyClass[5];→ 只加载并链接MyClass,但不初始化;实际生成的是数组类型[LMyClass; -
Class.forName("MyClass", false, loader)或ClassLoader.loadClass("MyClass")→ 第二个参数为false时,只加载不初始化 -
MyClass.class→ 获取 Class 对象,不触发初始化(这是最常被误认为会初始化的操作)
核心判断依据:有没有“第一次实质性读写”
是否初始化,不看代码里写了什么类名,而看是否对类的静态成员做了首次实质性访问:
- 首次读取一个非编译期常量的
static字段,且该字段所在类尚未初始化 → 主动使用 → 触发初始化 - 调用静态方法、
new实例、反射调用Class.forName("X")(默认initialize=true)→ 主动使用 - 仅出现类名、仅创建数组、仅获取
.class或loadClass→ 被动引用,不触发初始化

















