准备阶段为静态变量分配内存并设零值,仅final static编译期常量(基本类型/String且编译期可确定)在此阶段赋真实值。

准备阶段(Preparation)是 JVM 类加载过程中的第三个阶段,发生在加载和验证之后、解析之前。它不执行任何 Java 代码,只做两件事:为类的静态变量在方法区分配内存,并设置初始值。但这个“初始值”不是你写的值,而是根据类型决定的零值(zero value),除非是满足编译期常量条件的 final static 字段——后者会在准备阶段直接赋真实值。
静态变量:分配内存 + 设零值
所有未被 final 修饰的静态变量,在准备阶段仅获得默认值:
- 基本类型:int/short/byte → 0,long → 0L,float → 0.0f,double → 0.0d,char → '\u0000',boolean → false
- 引用类型:全部设为 null(包括 String、自定义类、数组等)
- 此时哪怕你写了
static int count = 100;,count 在准备阶段仍是 0;真正赋 100 是在后续的初始化阶段(<clinit></clinit>执行时)
final static 常量:准备阶段就写入真实值
只有同时满足以下三个条件的字段,才被视为“编译期常量”,JVM 会在准备阶段直接赋予你写的值:
- 用
public static final修饰(访问权限不影响,但通常这么写) - 类型是基本类型或 String
- 初始化表达式在编译期可完全确定(如字面量、常量表达式:
2 + 3、"hello"、Integer.MAX_VALUE)
例如:static final int PORT = 8080; 或 static final String NAME = "app"; —— 这些字段在准备阶段内存中就已是 8080 和 "app",不会等到初始化阶段。
立即学习“Java免费学习笔记(深入)”;
你接手一个需求时,是不是经常这样:"改一个小字段 → 结果把不相关的代码也改了"、"需求说得很模糊 → 做着做着发现跑偏了"、"数据库加个字段 → 上线才发现漏了数据兼容"、"做了个新功能 → 一个月后自己都忘了当时怎么设计的"、"写到一半被中断 → 再回来不知道从哪继续";这个 Skill 就是专门解决这些问题的。 它会自动探测你的项目环境(JDK、Spring Boot、ORM、数据库、前端框架),记在
反例:static final long TS = System.currentTimeMillis(); 不是编译期常量,仍走初始化阶段赋值;static final List<string> LIST = new ArrayList();</string> 类型不符(非基本类型/String),也不行。
为什么这样设计?
编译期常量会被内联到调用处(比如其他类引用它时,可能直接替换成字面量),所以必须在类加载早期就确定值;而普通静态变量可能依赖运行时计算(如调用方法、读配置),只能延迟到初始化阶段执行。
你可以用 javap -v 查看 class 文件,会发现这类常量字段的属性表里有 ConstantValue 属性,JVM 就是靠它在准备阶段直接填值的。
实际影响:别假设 static 变量“一加载就有值”
很多问题源于误判时机:
- 反射获取某个静态字段值时,如果类尚未初始化,非 final static 字段还是默认值(如 null),不是你写的初值
- 子类访问父类的 public static 字段,若该字段是非 final 的,触发的是子类加载而非父类初始化,父类静态块可能根本没执行
- 日志或调试中看到 static 变量为 0 或 null,不一定代码写错了,很可能是还没走到初始化阶段

















