准备阶段仅对被static修饰且非编译期常量的类变量赋零值:int/short/byte/char→0,long→0l,float→0.0f,double→0.0d,boolean→false,引用类型→null;实例变量、static final编译期常量、显式初始化表达式均不在此阶段赋值。

在 Java 类加载的准备阶段,只给类变量(static 变量)分配内存并设置初始零值,不执行任何 Java 代码,也不调用 <clinit></clinit> 方法。也就是说,这个阶段的“赋初值”不是你写的默认值,而是 JVM 按数据类型硬编码的零值。
哪些变量会在准备阶段被赋零值?
仅限被 static 修饰的类变量(即静态字段),且必须是已在类中声明、未被 final 修饰或虽 final 但尚未在编译期确定常量值的变量。
- int / short / byte / char → 0
- long → 0L
- float → 0.0f
- double → 0.0d
- boolean → false
- reference 类型(如 String、Object)→ null
哪些赋值行为 不会 在准备阶段发生?
以下情况均跳过准备阶段的零值设定,或在后续阶段才生效:
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
- 实例变量(非 static)不参与准备阶段 —— 它们在对象实例化(new)时,在堆中分配并设零值。
-
static final 的基本类型/字符串字面量常量 —— 如
public static final int MAX = 100;,这类在编译期就确定了常量值,JVM 直接将 100 写入常量池,准备阶段直接设为 100(不是零),跳过零值再覆盖的过程。 -
static 变量的显式初始化表达式 —— 如
static int x = getValue();或static List<string> list = new ArrayList();</string>,这些逻辑全部推迟到初始化阶段(执行<clinit></clinit>方法时)才真正运行。
一个典型对比示例
看这段代码:
class Demo {
static int a = 10; // 准备阶段:a = 0;初始化阶段:a = 10
static final int b = 20; // 编译期常量,准备阶段直接设为 20(不经过零值)
static final int c = (int)Math.PI; // 非编译期常量(Math.PI 是运行时计算),准备阶段设 0,初始化阶段再赋值
static Object obj = new Object(); // 准备阶段:obj = null;初始化阶段:执行 new 分配对象并赋值
}
注意:c 虽然用了 final,但因右值不是编译期常量(Math.PI 是 double,强制转 int 属于运行期计算),所以它不享受“编译期常量优化”,仍走标准流程:先零值,后初始化。
怎么验证准备阶段的行为?
可通过 JVMTI、字节码工具(如 jclasslib)查看类的 ConstantValue 属性,或用反射+断点配合 HotSpot 调试器观察字段值变化时机。更轻量的方式是写个极简测试类,在 <clinit></clinit> 开头加日志或断点,观察 static 字段在断点前的值 —— 往往就是零值或常量池值,而非你写的初始化表达式结果。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










