准备阶段在方法区为类变量分配内存并设零值,即按字段类型预留槽位并真实清零,确保安全初始化;static final编译期常量直接写入值,运行期常量则先置零后覆写。

准备阶段为类变量分配内存并设置初始零值,不是在堆里“new出一个对象”,也不是在栈里存个局部变量,而是 JVM 在方法区(元空间)中划出一块固定结构的存储区域,用于存放该类所有静态字段的值——这个动作是类加载过程中的标准化步骤,有明确的物理内存映射含义。
方法区里的“分配内存”到底是什么意思
方法区是线程共享的运行时数据区,它存储的是类型结构信息 + 静态变量的值容器。所谓“为类变量分配内存”,本质是:
- JVM 解析类的字节码时,从
static字段表中读取字段名、描述符(如I表示 int)、访问标志等; - 在方法区中为每个非
final的静态字段预留一个固定大小的槽位(slot),比如int占 4 字节,long/double占 8 字节,引用类型占一个机器字长(如 8 字节); - 这些槽位被组织成连续或索引可寻址的一组位置,与类的元数据(如常量池、字段表指针)一起构成该类在方法区的完整镜像。
就像图书馆给每本书预留一个固定编号的书架格子——格子本身不装内容,但位置已定、尺寸已配、编号已登记。
“设置默认零值”是写入物理内存还是逻辑约定
是真实写入内存,不是“假装有值”。JVM 在完成上述槽位分配后,会执行一次内存清零操作:
- 对数值型字段(
int/short/byte/char/boolean/long/float/double),把对应槽位的字节全部设为0x00; - 对引用类型字段(如
static String s;),把对应槽位设为全0——在 64 位 JVM 中即0x0000000000000000,这正是null的二进制表示; - 这一步由 JVM 底层 C++ 代码调用
memset()或等效指令完成,发生在类加载的 prepare 阶段,早于任何 Java 字节码执行。
为什么必须是“零值”,而不是保持未定义状态
这是 JVM 规范强制要求的安全机制:
- 避免读取到内存中残留的随机垃圾数据(如上一个对象释放后留下的旧值);
- 保证跨平台行为一致:无论底层操作系统如何管理内存页,Java 程序员看到的
static int x;永远是0,不是0xCAFEBABE或其他不可控值; - 与数组初始化规则对齐(
new int[1]元素也是0),形成统一的“默认安全初始化”语义。
和 static final 编译期常量的区别在哪
关键在于写入时机和来源:
-
static final int MAX = 100;:100 是编译期确定的常量,JVM 直接把100的二进制(如0x00000064)写入方法区对应槽位,跳过“先写 0 再覆盖”的过程; -
static final long TIME = System.nanoTime();:值只能运行时算出,prepare 阶段仍写0L(0x0000000000000000),到 initialization 阶段才真正调用方法并覆写; - 二者在方法区中占用的是同一块物理槽位,区别只是“谁来写”和“什么时候写”。
不复杂但容易忽略










