准备阶段核心是分配内存与设初值,普通static变量仅置零值,编译期常量(static final+编译期可确定值)则直接赋源码值;前者依赖<clinit>初始化,后者由ConstantValue属性驱动、加载即生效。

准备阶段的核心动作是“分配内存”和“设初值”,但两类静态变量的处理逻辑截然不同
准备阶段不执行任何 Java 代码,只由 JVM 主动完成底层内存操作。关键区别在于:普通类变量(static 变量)只分配内存并设零值;而编译期常量(static final + 编译期可确定值)会在同一阶段直接赋予源码指定值。这不是程度差异,而是机制质变——前者靠运行时默认填充,后者靠编译器注入元信息驱动赋值。
普通类变量:内存已就位,值仍是零值
例如 public static int count = 42;:
- JVM 在方法区(JDK8+为元空间)为其分配 4 字节内存空间
- 但该内存内容被明确写入 0,不是 42
- 你写的
= 42被编译成putstatic指令,收进<clinit>方法,留到初始化阶段才执行 - 若此时通过反射读取 count,得到的是 0,而非预期的 42
编译期常量:内存分配即完成业务赋值
例如 public static final String NAME = "Alice"; 或 public static final int MAX = 100;:
- javac 编译时识别出这是编译期常量,将其字面值写入 class 文件的常量池,并添加
ConstantValue属性 - JVM 在准备阶段解析该属性,跳过零值设定,直接把 "Alice" 或 100 写入对应内存位置
- 该赋值不依赖
<clinit>,也不受类初始化顺序影响;哪怕类从未被主动初始化,只要被加载,这个值就已生效 - 注意:若写成
public static final int CODE = System.currentTimeMillis() % 100;,因无法在编译期确定,就失去此待遇,退化为普通 static final 变量(仅初始化阶段赋值)
判断是否属于编译期常量,看三个硬条件
必须同时满足:
- 用
static final修饰 - 类型是基本类型或 String(其他引用类型即使 final 也不行)
- 初始化表达式在编译期能完全求值(仅含字面量、常量变量、简单运算符,不含方法调用、new、instanceof 等)
三者缺一不可。比如 static final long T = 1000L * 60 * 60 * 24; 是编译期常量;而 static final Date NOW = new Date(); 不是。

















