Java类是否初始化取决于引用是主动还是被动:主动引用(如new、访问非编译期常量静态字段、调用静态方法、Class.forName、子类初始化父类未初始化)触发<clinit>执行;被动引用(如子类访问父类静态字段、数组创建、.class获取、编译期常量访问)仅加载链接,不初始化。

Java 类加载过程中,是否触发类的初始化,关键看引用是“主动”还是“被动”。不是所有出现类名的地方都会初始化类,JVM 严格按规范判断——只有主动引用才执行 <clinit>(静态初始化块和静态变量赋值),被动引用只走到加载、链接阶段,跳过初始化。
主动引用:必须初始化的五种典型场景
以下操作一旦发生,且目标类尚未初始化,JVM 就会强制完成其初始化(包括父类):
-
new 实例化对象:如
new User(),触发User类初始化 -
读写非编译期常量的静态字段:如
User.count++或System.out.println(User.name)(name不是static final基本类型或字符串字面量) -
调用静态方法:如
User.login(),无论方法体是否为空 -
反射触发:如
Class.forName("User")(无参或第二个参数为true) -
子类初始化时父类未初始化:如首次访问
SubUser.id(SubUser有静态块),而User(父类)尚未初始化,则先初始化User
被动引用:只加载不初始化的常见情况
这些操作仅引起类的加载与链接(验证、准备、解析),但绝不会执行静态初始化代码:
-
通过子类访问父类静态字段:如
System.out.println(SubUser.PORT),其中PORT定义在User中且为public static final int PORT = 8080→User类可能都不加载;若PORT是public static int PORT = 8080(非 final),则User加载并链接,但不执行其<clinit> -
数组声明与创建:如
User[] users = new User[10]→ JVM 加载数组类[LUser;,但User类本身不初始化 -
获取 Class 对象:如
User.class或ClassLoader.loadClass("User")→ 仅完成符号引用解析,不触发初始化 -
访问编译期常量:如
Config.VERSION,若定义为public static final String VERSION = "1.0"→ 值被“内联”进调用方常量池,Config类全程不参与加载
核心判断逻辑:有没有第一次实质性读写
JVM 不看代码写了什么类名,而看是否发生了对类静态成员的首次实质性访问:
立即学习“Java免费学习笔记(深入)”;
- “实质性”指真正读取或修改运行时内存中的静态变量值,或调用静态方法体
-
static final基本类型或字符串字面量 → 编译期确定 → 无运行时访问 → 不触发 -
static final Integer、static final String new String("x")、static final int x = calc()→ 运行期才能确定 → 属于主动使用 → 触发初始化 - 子类中出现
User.count++或System.out.println(User.count)→ 构成对User的主动引用 → 立即初始化User,但SubUser自身仍不初始化(除非它也被主动引用)
调试与验证技巧
可通过 JVM 参数观察实际加载行为:
-
-XX:+TraceClassLoading:打印每个被加载的类名(含数组类),确认是否加载 -
-XX:+TraceClassInitialization:只打印真正完成初始化的类(配合日志中的init输出) - 在静态块中加
System.out.println("Xxx init"),是最直观的验证方式


















