1. 概述
本文深入探讨 JVM 如何在堆内存中布局对象和数组。
我们先从理论入手,再结合 HotSpot JVM 的实际内存布局进行分析。需要明确的是,运行时数据区的内存布局并未在 JVM 规范中强制定义,而是由具体实现自行决定(JVM Spec 引用)。因此,不同 JVM 实现可能采用不同的对象布局策略。
本文聚焦于 HotSpot JVM 的实现机制,后续提到的 JVM 均指 HotSpot。
⚠️ 注意:虽然文中会使用“JVM”简称,但所有分析均基于 HotSpot 实现。
2. 普通对象指针(OOPs)
HotSpot 使用一种称为 Ordinary Object Pointers(OOPs) 的数据结构来表示对象指针。所有对象和数组的指针都基于 oopDesc
结构体构建。
每个 oopDesc
包含两个核心部分:
- ✅ Mark Word:存储对象元信息,如哈希码、锁状态、GC 标记等
- ✅ Klass Word:指向类元数据(
Klass
),包含类名、修饰符、父类等信息
Mark Word 详解
Mark Word 是对象头的关键部分,用于存储:
- 身份哈希码(Identity Hash Code)
- 锁信息(偏向锁、轻量级锁、重量级锁)
- 垃圾回收相关元数据(如分代年龄)
⚠️ 其大小在 32 位 JVM 中为 4 字节,64 位中为 8 字节。
⚠️ Java 15 起已弃用偏向锁(JEP 374),本文仅讨论普通对象。
Klass Word 详解
Klass Word 指向 Klass
结构,封装了语言层面的类信息。在 64 位 JVM 中,若启用压缩指针(Compressed Oops),该字段为 4 字节;否则为 8 字节。
对象头总览
类型 | 组成 | 最小大小(64位) |
---|---|---|
普通对象 | Mark Word (8B) + Klass Word (4B) + 填充 | 16 字节 |
数组对象 | 普通对象头 + 4 字节长度字段 | 16 字节 |
✅ 所有对象大小都会对齐到 8 字节倍数(默认),因此即使字段总和不足也会填充。
3. 使用 JOL 分析内存布局
要直观查看对象内存布局,推荐使用 Java Object Layout(JOL) 工具。添加依赖:
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.10</version>
</dependency>
初始化查看 VM 配置:
System.out.println(VM.current().details());
输出示例:
# Running 64-bit HotSpot VM.
# Objects are 8 bytes aligned.
# Field sizes by type: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
# Array element sizes: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
含义:
- 引用:4 字节(压缩指针开启)
- boolean/byte:1 字节
- short/char:2 字节
- int/float:4 字节
- long/double:8 字节
❌ 若关闭压缩指针(-XX:-UseCompressedOops
),引用变为 8 字节。
4. 内存布局实战案例
4.1 基础对象布局
public class SimpleInt {
private int state;
}
分析类布局:
System.out.println(ClassLayout.parseClass(SimpleInt.class).toPrintable());
输出:
SimpleInt object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 12 (object header) N/A
12 4 int SimpleInt.state N/A
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
✅ 12 字节对象头(8B mark + 4B klass) + 4B int = 16B,无需额外填充。
4.2 身份哈希码存储
未重写 hashCode()
时,JVM 使用身份哈希码,并懒加载存储在 Mark Word 中。
SimpleInt instance = new SimpleInt();
System.out.println(ClassLayout.parseInstance(instance).toPrintable());
初始状态(未计算哈希码):
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 00 00 00
4 4 (object header) 00 00 00 00
8 4 (object header) 9b 1b 01 f8
12 4 int SimpleInt.state 0
调用 System.identityHashCode(instance)
后:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 25 b2 74
4 4 (object header) 65 00 00 00
8 4 (object header) 9b 1b 01 f8
12 4 int SimpleInt.state 0
✅ 哈希码 1702146597
以小端序存储为 25 b2 74 65
→ 反序 65 74 b2 25
即为原值。
4.3 内存对齐机制
JVM 默认将对象大小对齐到 8 字节倍数。
public class SimpleLong {
private long state;
}
布局分析:
SimpleLong object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 12 (object header) N/A
12 4 (alignment/padding gap)
16 8 long SimpleLong.state N/A
Instance size: 24 bytes
✅ 头部 12B + long 8B = 20B → 填充 4B 达到 24B(8 的倍数)。
可通过 -XX:ObjectAlignmentInBytes=16
修改对齐单位,此时总大小变为 32B。
4.4 字段重排序优化
JVM 会自动重排字段顺序以减少填充浪费。
public class FieldsArrangement {
private boolean first;
private char second;
private double third;
private int fourth;
private boolean fifth;
}
实际布局:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 12 (object header) N/A
12 4 int FieldsArrangement.fourth N/A
16 8 double FieldsArrangement.third N/A
24 2 char FieldsArrangement.second N/A
26 1 boolean FieldsArrangement.first N/A
27 1 boolean FieldsArrangement.fifth N/A
28 4 (loss due to the next object alignment)
✅ JVM 按 int → double → char → boolean 降序排列,最大限度减少内部碎片。
4.5 锁信息存储
锁状态也存储在 Mark Word 中。
public class Lock {}
无锁状态:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 00 00 00
4 4 (object header) 00 00 00 00
8 4 (object header) 85 23 02 f8
12 4 (loss due to the next object alignment)
加锁后(synchronized):
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) f0 78 12 03
4 4 (object header) 00 70 00 00
8 4 (object header) 85 23 02 f8
✅ Mark Word 位模式变化,记录了锁信息。
4.6 分代年龄(GC 年龄)
对象在 Survivor 区经历一次 Minor GC,年龄 +1,该信息也存在 Mark Word。
模拟代码:
volatile Object consumer;
Object instance = new Object();
long lastAddr = VM.current().addressOf(instance);
for (int i = 0; i < 10_000; i++) {
long currentAddr = VM.current().addressOf(instance);
if (currentAddr != lastAddr) {
System.out.println(ClassLayout.parseInstance(instance).toPrintable());
}
for (int j = 0; j < 10_000; j++) {
consumer = new Object(); // 制造垃圾
}
lastAddr = currentAddr;
}
Mark Word 变化趋势:
09 00 00 00 → age=1
11 00 00 00 → age=2
19 00 00 00 → age=3
21 00 00 00 → age=4
...
✅ 高 4 位记录 GC 年龄,每次晋升年龄递增。
4.7 避免伪共享:@Contended
@jdk.internal.vm.annotation.Contended
注解用于避免伪共享(False Sharing),通过填充使字段独占缓存行。
public class Isolated {
@Contended
private int v1;
@Contended
private long v2;
}
布局结果:
Isolated object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 12 (object header) N/A
12 128 (alignment/padding gap)
140 4 int Isolated.i N/A
144 128 (alignment/padding gap)
272 8 long Isolated.l N/A
Instance size: 280 bytes
✅ 每个 @Contended
字段前后填充 128 字节(典型缓存行大小 64~128B)。
⚠️ 注意事项:
- 该注解属于 JDK 内部 API,不建议生产使用
- 需添加 JVM 参数:
-XX:-RestrictContended
才能生效 - 可通过
-XX:ContendedPaddingWidth
调整填充大小
4.8 数组内存布局
数组对象头额外包含 4 字节长度字段。
boolean[] booleans = new boolean[3];
System.out.println(ClassLayout.parseInstance(booleans).toPrintable());
输出:
[Z object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 00 00 00 # mark
4 4 (object header) 00 00 00 00 # mark
8 4 (object header) 05 00 00 f8 # klass
12 4 (object header) 03 00 00 00 # array length
16 3 boolean [Z.<elements> N/A
19 5 (loss due to the next object alignment)
Instance size: 24 bytes
✅ 总头大小 16B(mark 8B + klass 4B + length 4B)。
4.9 压缩指针失效场景
当堆内存超过 32GB 或手动关闭压缩指针(-XX:-UseCompressedOops
),Klass Word 变为 8 字节。
数组示例(关闭压缩指针):
[Z object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 00 00 00 # mark
4 4 (object header) 00 00 00 00 # mark
8 4 (object header) 28 60 d2 11 # klass
12 4 (object header) 01 00 00 00 # klass (高位)
16 4 (object header) 03 00 00 00 # length
20 4 (alignment/padding gap)
24 3 boolean [Z.<elements> N/A
✅ Klass 占用 8 字节(偏移 8~15),总对象头变为 20B。
5. 总结
本文系统解析了 HotSpot JVM 中对象与数组的内存布局机制,涵盖:
- ✅ OOPs 结构与对象头组成
- ✅ Mark Word 的多功能复用(哈希、锁、GC)
- ✅ 字段重排序与内存对齐优化
- ✅
@Contended
解决伪共享 - ✅ 数组长度存储位置
- ✅ 压缩指针的生效条件
🔧 进阶建议:
- 源码参考:HotSpot oops 模块
- 深度文章:Aleksey Shipilëv 的 《Objects Inside Out》
- JOL 示例:GitHub 仓库
所有示例代码已托管至 GitHub:github.com/yourname/jvm-memory-layout-demo