类加载发生在第一次主动使用时,而非定义或new时;主动使用包括实例化、调用静态方法、访问非final静态字段、反射获取Class(需初始化)、子类初始化、JVM启动主类等七种情形。

类加载发生在第一次主动使用时,不是定义时也不是new时
Java 类不会在编译完成或类文件被读入 JVM 时立刻加载。真正触发 ClassLoader.loadClass() 并执行链接、初始化的,是「主动使用」——这是 JVM 规范明确定义的行为。很多人误以为 new MyClass() 或声明变量就加载,其实不然:如果只是引用了类名但没触发任何主动行为,类可以一直不加载。
- 主动使用包括:
new实例化、调用静态方法、访问(读或写)静态字段(final常量除外)、反射获取Class对象、子类初始化导致父类初始化、JVM 启动时指定的主类 -
final static基本类型常量(如public static final int VAL = 42;)在编译期就内联到调用处,访问它不会触发类加载 - 声明变量(
MyClass obj;)、继承、实现接口、import,都不算主动使用
静态变量初始化只在类初始化阶段执行一次,且按代码顺序
静态变量赋值和静态代码块(static {})都在类的「初始化阶段」执行,且严格按源码中出现的顺序从上到下执行。这个阶段只发生一次,由 JVM 保证线程安全(通过类初始化锁)。
- 如果静态变量依赖尚未初始化的其他静态字段,会得到默认值(如
int是0,引用类型是null),不是抛错 - 静态初始化块里抛出未捕获异常,会导致
NoClassDefFoundError后续所有对该类的主动使用 - 子类初始化时,父类一定已完成初始化;但父类静态字段被子类继承后访问,仍只触发父类初始化(不会重复)
class Parent {
static { System.out.println("Parent init"); }
static int x = getValue();
static int getValue() { return y + 1; } // 注意:此时 y 还未赋值,返回 0+1=1
static int y = 2;
}
上面代码输出 Parent init,x 最终是 1,y 是 2 —— 因为 getValue() 调用时 y 还处于默认值 0 状态。
容易踩坑:看似“用了”其实没触发类加载
很多调试场景下你以为类已经加载并初始化了,结果发现静态块没执行、静态变量还是默认值,本质是触发条件没满足。
立即学习“Java免费学习笔记(深入)”;
- 仅通过反射获取
Class对象(Class.forName("MyClass", false, loader))且第二个参数为false,就不会初始化类 - 用
ClassLoader.getSystemClassLoader().loadClass("MyClass")也不会触发初始化,只完成加载和链接 - 访问
final static字符串或基本类型字段(如MyClass.CONST),若该字段在编译期可确定,则根本不会走到类加载流程 - 泛型擦除后,
List<MyClass>不构成对MyClass的主动使用
验证类是否已加载/初始化:用 JVM 参数或运行时检查
光看代码逻辑容易误判,实际运行中需要工具辅助确认状态。
- 加
-verbose:class启动 JVM,能看到每个类加载的时机和来源(注意:这不等于初始化) - 加
-XX:+TraceClassInitialization可精确打印哪些类进入了初始化阶段(JDK 8u261+ 支持) - 运行时用
Class.forName("MyClass", true, loader)强制触发初始化(第二个参数true表示要初始化) -
MyClass.class.desiredAssertionStatus()这类反射调用,只要类已加载就会成功;但如果类根本没加载,会触发加载+初始化(因为.class字段访问属于主动使用)
final 修饰符,行为就完全不同。最麻烦的是那些“看起来应该发生了,但其实没发生”的情况——这时候得回到规范查主动使用的七种情形,而不是凭经验猜。








