Java对象头包含Mark Word和Class Pointer(普通对象)或额外Array Length字段(数组对象),其大小受压缩指针开关影响:开启时普通对象头12字节,关闭时16字节;空Object因需8字节对齐而占16字节。

对象头里到底存了啥
Java对象在堆内存里的布局,对象头是第一部分,它不是固定大小,而是分两种情况:普通对象和数组对象。普通对象头包含 Mark Word 和 Class Pointer;数组对象多一个 Array Length 字段(4字节)。Mark Word 会复用存储锁状态、GC分代年龄、哈希码等信息,具体存什么取决于当前锁状态——比如无锁时存哈希码,轻量级锁时存指向栈中锁记录的指针。
注意:开启 -XX:+UseCompressedClassPointers(默认开启)时,Class Pointer 占4字节而非8字节;但若堆大于32GB,压缩指针失效,它就变成8字节。这直接影响对象头总长,进而影响后续字段对齐。
- 64位 JVM + 压缩类指针 + 普通对象 → 对象头 = 8(Mark Word) + 4(Class Pointer) = 12 字节
- 64位 JVM + 关闭压缩类指针 → 对象头 = 8 + 8 = 16 字节
- 数组对象额外 + 4 字节
Array Length
字段对齐与补齐填充怎么算
JVM要求对象起始地址必须是8字节的整数倍,同时每个字段也要按自身宽度自然对齐(比如 long 和 double 要求8字节对齐)。字段按宽度从大到小排序后插入(HotSpot 实现),再在末尾补零,凑够8字节倍数。
举个例子:class A { byte a; long b; int c; } 字段排布不是按声明顺序,而是先放 b(8字节),再放 c(4字节),再放 a(1字节),最后补7字节填充 → 总大小 = 8 + 4 + 1 + 7 = 20 字节,但对象总大小必须是8的倍数,所以最终是24字节。
立即学习“Java免费学习笔记(深入)”;
- 字段重排序只发生在对象实例字段之间,不包括父类字段(父类字段在前,子类字段在后)
- 静态字段、常量池、方法区内容不参与对象布局计算
- 使用
-XX:+PrintFieldLayout可直接打印 HotSpot 的字段排布结果,比手动算靠谱
为什么 java.lang.Object 占16字节
空对象也要满足对齐要求。Object 没有实例字段,只有对象头。在默认压缩指针下,对象头共12字节(8+4),但12不是8的倍数,所以 JVM 在末尾加4字节填充 → 总大小16字节。
这个“16字节”是常见误区来源:有人以为是头占16,其实是头+填充。一旦你加一个 byte 字段,对象大小不会变成17,而是仍为16(因为 byte 塞进填充区),直到填满才扩容。
-
new Object()→ 16 字节 -
class B { byte x; }→ 还是 16 字节(x 填进那4字节空隙) -
class C { byte x; long y; }→ 24 字节(y 需要8字节对齐,前面12字节头 + 1字节x + 3填充 + 8字节y = 24)
排查布局问题时最容易忽略的点
对象大小不是靠 sizeof 或反射能直接拿到的,必须用工具。JOL(Java Object Layout)是事实标准,但要注意它的输出依赖 JVM 参数,尤其是是否启用压缩指针、是否开启分层编译等。
另一个坑是:对象数组(Object[])本身是对象,其元素引用是4或8字节,但引用指向的每个 Object 实例又各自占16字节——这里容易把“数组对象大小”和“数组里所有元素总内存”混为一谈。
- 别信 IDE 的“估算大小”,它不考虑对齐和填充
-
Instrumentation.getObjectSize()返回的是 shallow size(不含引用对象),且不反映实际内存布局细节 - 用 JOL 看布局前,先确认 JVM 启动参数和目标 JDK 版本,不同版本字段排序策略可能微调
对象布局真正麻烦的地方不在结构本身,而在它被 JVM 实现细节、CPU 缓存行、GC 算法共同牵制——改一个字段类型,可能让填充变多、缓存局部性变差、GC 扫描成本上升。这些影响往往藏在压测数据里,而不是日志里。







