类初始化触发于主动使用:new实例、访问静态非常量字段、调用静态方法、Class.forName、子类初始化或主类启动;执行时严格遵循父类优先、静态先于实例、源码书写顺序。

类初始化的时机与顺序,核心在于区分“什么时候触发”和“执行时按什么顺序走”。它不是一上来就全做,而是分阶段、有条件、有依赖地推进。
哪些操作会真正触发类初始化
只有“主动使用”才会启动初始化阶段,被动声明或引用不会。常见触发点包括:
- 用 new 创建该类或其子类的实例
- 访问该类的静态非常量字段(比如
public static int x = 10;;但public static final int VAL = 100;编译期内联,不触发) - 调用该类的静态方法
- 通过 Class.forName("X") 反射加载(注意:
ClassLoader.loadClass()不触发初始化) - 初始化一个子类时,若父类尚未初始化,则先初始化父类
- JVM 启动时指定的主类(含
main方法的类)
初始化内部的执行顺序是确定的
一旦触发,JVM 严格按规则执行,不依赖编译器优化:
- 父类优先:先完成父类全部静态初始化,再轮到子类
-
静态先于实例:所有静态内容(变量显式赋值 +
static{})只执行一次,且在任何对象创建前完成 - 文本顺序为准:同一类中,静态变量声明、静态代码块按源码从上到下依次执行
例如:new Child() 会依次输出:Parent静态块 → Child静态块 → Parent实例块 → Parent构造器 → Child实例块 → Child构造器。
立即学习“Java免费学习笔记(深入)”;
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
准备阶段和初始化阶段不能混淆
很多人误以为“声明即赋值”发生在加载时,其实不然:
-
准备阶段:为
static变量分配内存,并设默认值(int为 0,Object为null),不执行任何 Java 代码 -
初始化阶段:才真正运行
<clinit>方法,执行静态变量的显式赋值语句(如static int x = 5;中的= 5)和所有static{}块
也就是说,“分配内存”在准备阶段,“赋具体值”在初始化阶段——这是理解日志输出顺序的关键。
继承场景下容易忽略的细节
子类引用父类静态字段,只初始化父类,子类可能连 static{} 都不执行:
-
System.out.println(SubClass.inheritedValue);(该字段定义在SuperClass中)→ 只触发SuperClass初始化 -
SubClass[] arr = new SubClass[10];→ 仅加载SubClass类,不触发任何初始化 - 数组类型本身不是类,
SubClass只是元素类型,JVM 不认为这是对它的“主动使用”
这种“按需加载、最小初始化”的设计,保障了性能与语义的一致性。

















