new Child() 先打印 Grandparent 静态块,因为 JVM 类初始化遵循父类优先原则:触发 Child 初始化前必须先完成 Parent 和 Grandparent 的初始化,且静态内容按源码声明顺序执行。

类加载顺序不是“代码执行顺序”,而是JVM对类结构进行初始化的严格阶段链:加载→验证→准备→解析→初始化,其中静态内容初始化(static字段和块)按源码声明顺序触发,且父类优先于子类。
为什么 new Child() 会先打印 Grandparent 静态块?
因为 JVM 触发类初始化时,必须确保其直接父类已初始化完毕——这是“父类优先原则”,由规范强制保证,与你是否显式调用无关。
- 只要
Child类首次被主动使用(如new Child()、访问Child.staticField),JVM 就检查它是否已初始化;未初始化则先触发Parent初始化,而Parent又强制先完成Grandparent初始化 - 静态变量赋值和静态代码块按 Java 源码中从上到下的**文本顺序**执行,不是按声明类型(字段/块)分组
-
准备阶段只设默认值(如int设为0),真正赋值发生在初始化阶段——所以static int x = 5;的5是在初始化时才写入
哪些操作会真正触发类初始化?
只有六种“主动使用”场景才会强制进入初始化阶段,其他如单纯声明引用、子类引用父类静态字段(非 final)、数组创建等,都不会触发子类初始化。
- 遇到
new、getstatic、putstatic或invokestatic字节码指令,且对应类未初始化 - 使用
java.lang.reflect对类进行反射调用(如Class.forName("X"),注意带参版本默认initialize=true) - 初始化子类时,父类尚未初始化
-
虚拟机启动时标明的主类(即含
main方法的类) - 使用 JDK 7+ 的动态语言支持(如
invokedynamic)首次解析MethodHandle且对应类未初始化 -
java.lang.invoke.MethodHandle解析结果为REF_getStatic/REF_putStatic/REF_invokeStatic,且对应类未初始化
为什么 static final 常量不触发类初始化?
因为编译期就内联了——JVM 在 准备 阶段直接把常量值塞进调用方的常量池,根本不会走到 初始化 阶段去读原类。
立即学习“Java免费学习笔记(深入)”;
- 前提是:字段是基本类型或
String,且用字面量或编译期可确定的表达式初始化,如static final int PORT = 8080; - 若写成
static final int PORT = Integer.valueOf(8080);或static final String NAME = System.getProperty("name");,则仍会触发初始化 - 这个优化让常量访问零开销,但也意味着:如果通过反射修改该类的静态 final 字段(需先
setAccessible(true)),实际生效的是运行时值,但调用方看到的仍是编译期内联的老值
最容易被忽略的一点是:类加载的“五阶段”只是逻辑划分,真实执行中各阶段高度交叉——比如 解析 可能延迟到首次方法调用时才做(支持多态),而 验证 可能在加载过程中就部分执行。别死记流程图,要盯住“什么时机真正改变运行状态”。







