ObjectHeader实际占12字节(Mark Word 8B + Klass Pointer 4B),但需按8字节对齐补齐至16字节;数组对象头额外含4字节length字段,共16字节。

怎么看 ObjectHeader 实际占多少字节
别猜,直接用 JOL(Java Object Layout)工具看真实布局。JVM 的对象头不是固定结构,Mark Word 和 Klass Pointer 的大小受压缩开关、锁状态、是否数组等影响,光看文档容易误判。
运行以下代码就能拿到精确字节数:
import org.openjdk.jol.info.ClassLayout;
public class HeaderSize {
public static void main(String[] args) {
System.out.println(ClassLayout.parseInstance(new Object()).toPrintable());
}
}
关键看输出里 OFFSET 列和 SIZE 行的总和,不是简单加 8 + 4 就完事——因为字段重排和对齐填充会动态介入。
- 必须在目标 JVM 参数下运行(比如带
-XX:+UseCompressedOops) - 注意区分
new Object()(无实例字段)和带字段对象,后者可能触发字段重排,间接改变头尾边界 - 数组对象额外多出 4 字节
array length字段,OFFSET从 12 变成 16(压缩开启时)
-XX:+UseCompressedOops 对 Klass Pointer 的实际影响
这个参数只压缩普通对象引用(OOPs),但 Klass Pointer 是另一套开关:-XX:+UseCompressedClassPointers。两者默认都开,但作用域不同,混淆会导致误算对象头。
验证方式:
- 运行
java -XX:+PrintCommandLineFlags -version,确认两参数是否生效 - 若只关
UseCompressedOops而没关UseCompressedClassPointers,Klass Pointer仍为 4 字节,对象头不会涨到 16 字节 - 真正让对象头从 12 字节 → 16 字节,需要同时关闭两个压缩(64 位 JVM 下)
常见错误:看到 UseCompressedOops 关了,就以为所有指针都变 8 字节,结果 Klass Pointer 还是 4 字节,导致内存估算偏差。
为什么 Mark Word 看起来“不固定”
Mark Word 是 8 字节定长存储区,但内容复用严重——同一块 64-bit 区域,在不同锁状态下存不同东西。它不“变长”,只是语义切换。
典型场景:
- 刚创建未锁对象:
hashCode和GC age共存于低 32 位+高 32 位 - 进入偏向锁:
thread ID替换掉部分hashCode位,原位置被覆盖 - 升级为重量级锁:
Mark Word存的是指向Monitor的指针,hashCode被挪到对象 Monitor 里保存
所以用 JOL 看到的 Mark Word 值,取决于你调用时对象所处的状态。想稳定观测,得配合 synchronized 块或 Unsafe.monitorEnter 控制锁状态。
对齐填充怎么悄悄改掉你的预期大小
对象头 + 实例数据总和不是 8 的倍数时,JVM 自动补 Padding。这步常被忽略,但它能让你的“理论大小”和实测差 1–7 字节。
举例:new Object() 在压缩开启时:头 = 8(Mark Word) + 4(Klass Pointer) = 12 → 补 4 字节 → 实际占 16 字节。
但如果你加一个 byte 字段,实例数据变成 1 字节,总和 = 12 + 1 = 13 → 补 3 字节 → 还是 16 字节;再加一个 int(4 字节),总和 = 12 + 1 + 4 = 17 → 补 7 字节 → 实际 24 字节。
陷阱在于:字段顺序会影响填充量。JVM 会按宽度重排字段(long/double 优先靠前),所以声明顺序 ≠ 内存顺序,JOL 的 OFFSET 列才是唯一可信依据。










