引用类型声明不触发类初始化,静态块仅在类首次主动使用时执行一次;被动引用(如变量声明、数组创建)只可能触发加载和准备,不执行静态块;主动使用(如new、调用静态方法、访问非编译期常量静态字段)才触发初始化,且静态块严格只执行一次。

引用类型声明本身不触发类加载,静态块只在类首次主动使用时执行一次——这是理解Java初始化行为的关键前提。
引用类型声明不会触发类初始化
写一句 Parent p; 或 Child[] arr = new Child[3];,只是声明变量或创建数组,JVM仅完成类的“被动引用”:可能走到加载和准备阶段(比如分配静态变量默认值),但不会执行任何静态代码块,也不会给静态变量赋显式值。此时类尚未初始化,静态块里的日志不会打印,数据库连接也不会建立。
- 真正触发初始化的是“主动使用”,例如 new Child()、Child.doStaticMethod()、System.out.println(Child.NAME)(NAME 非编译期常量)
- 访问 public static final String MSG = "ok"; 这类编译期常量,连类加载都可能跳过,更不会执行静态块
静态块执行时机与唯一性
静态块属于类级别,由 JVM 在类的「初始化阶段」执行,且严格只执行一次,无论后续创建多少个对象。它的执行前提是类被首次主动使用,并已完成父类的静态初始化。
- 执行顺序固定:父类静态变量 → 父类静态块 → 子类静态变量 → 子类静态块(按源码文本顺序)
- 同一个类被不同类加载器加载(如自定义ClassLoader),会被视为两个独立类,各自执行一次静态块
- 若静态块中抛出未捕获异常,该类进入“初始化失败”状态,之后所有主动使用都会直接报 NoClassDefFoundError
对象创建时,静态与实例初始化完全分离
每次 new Child(),只走实例初始化流程,不会再碰静态块。JVM先分配内存并设默认值(0/false/null),再按顺序执行:
- 父类实例代码块({})→ 父类构造器
- 子类实例代码块({})→ 子类构造器
这个过程与静态部分彻底解耦:静态块已执行完毕,实例块则每次新建对象都重跑一遍。
常见误用与排查建议
开发中容易混淆的点往往出现在初始化依赖上:
- 静态块里调用的方法,不能依赖本类中后声明的静态字段(编译报错),但可安全使用父类 public static 成员
- 想在静态块里初始化某个工具类单例?确保该工具类已被主动使用过,否则可能触发其初始化,甚至引发循环依赖
- 调试时发现静态块没执行?检查是否真的发生了主动使用,而不是仅声明引用或访问了编译期常量











