基于本文回答

播面 播面

文图音视,全方位拆解八股文
0
评论

在类加载的“准备”阶段,JVM 是如何给类变量分配内存和赋初始值的?

知识点图片

在 JVM 的类加载机制中,准备(Preparation)阶段是链接(Linking)过程的第二步。在这个阶段,JVM 主要完成两件事:为类变量分配内存为类变量设置默认初始值

下面我将详细拆解这两个过程的具体实现细节:

一、 内存分配:给“谁”分配?分配到“哪”?

1. 给“谁”分配?(只针对类变量)

在准备阶段,JVM 只为类变量(即被 static 修饰的变量)分配内存,而不包括实例变量。

  • 类变量:跟随类的加载而分配,全类共享。
  • 实例变量:不会在这个阶段分配内存,它们会在对象实例化(new 操作)时,随着对象一起分配在 Java 堆中。

2. 分配到“哪”?(随 JDK 版本的变迁而不同)

根据《Java 虚拟机规范》,这些变量所使用的内存都应当在方法区(Method Area)中进行分配。但是,在 HotSpot 虚拟机的具体实现中,这个位置发生过变化:

  • JDK 6 及之前:静态变量存放在方法区的永久代(PermGen)中。
  • JDK 7 开始:HotSpot 将字符串常量池和静态变量从永久代中剥离,转移到了 Java 堆(Heap)中。静态变量被存放在与该类对应的 java.lang.Class 对象的末尾。
  • JDK 8 及之后:永久代被废除,取而代之的是元空间(Metaspace)。虽然类的元数据(类型信息、方法信息等)存放在本地内存的元空间中,但静态变量依然存放在 Java 堆中(紧挨着 Class 对象)。

二、 赋初始值:赋什么值?

在这个阶段,JVM 给类变量赋的是数据类型的默认零值(Zero Value),而不是我们在代码中显式赋予的值。

1. 默认零值列表:

数据类型 准备阶段的初始值
int 0
long 0L
short (short) 0
byte (byte) 0
char '\u0000' (空字符)
float 0.0f
double 0.0d
boolean false
引用类型 (如 String, Object) null

2. 经典例子:

假设你在代码里这样写:

java
public static int value = 123;
  • 在准备阶段:JVM 会为 value 分配内存,并将其初始值设置为 0
  • 为什么不是 123? 因为把 value 赋值为 123 的指令(putstatic)被编译到了类的初始化方法 <clinit>() 中。真正的赋值动作要等到类加载的最后一个阶段——初始化(Initialization)阶段才会执行。

三、 特例:ConstantValue 属性(被 final 修饰的常量)

虽然绝大多数类变量在准备阶段被赋为零值,但有一个重要的特例:如果类变量被 final 修饰,且在编译时就能确定其值,它会在准备阶段直接被赋予代码中指定的值。

在编译时,Javac 编译器会为这种变量生成一个 ConstantValue 属性。JVM 在准备阶段如果发现该变量带有 ConstantValue 属性,就会直接按此属性进行赋值。

对比示例:

java
// 普通类变量:准备阶段赋为 0,初始化阶段赋为 123
public static int A = 123; 

// 静态常量(带有 ConstantValue):准备阶段直接赋为 123
public static final int B = 123; 

// 静态常量(但不带有 ConstantValue,值要在运行期确定):准备阶段赋为 null,初始化阶段才调用方法赋值
public static final String C = new String("hello"); 

注:只有基本数据类型和字面量形式的 String 且被 static final 修饰,才会在准备阶段直接赋值。

总结

在 JVM 的准备阶段:

  1. 对象是谁:仅限 static 修饰的静态变量。
  2. 存放位置:逻辑上在方法区,HotSpot 实际实现中(JDK 7 起)存放在Java 堆的 Class 对象里。
  3. 赋初始值:普通静态变量赋默认零值(如 0, null, false);被 static final 修饰的字面量常量,直接赋代码中指定的真实值
00:00
00:00