openjdk对象在64位jvm中默认16字节对齐,对象头固定12字节(mark word 8字节 + 压缩klass pointer 4字节),字段重排优化布局,末尾填充至8字节对齐,指针压缩随堆大小超32gb自动关闭。

OpenJDK对象在64位JVM中默认按16字节对齐,且几乎总是以16字节为最小单位分配——不是因为语言规范要求,而是HotSpot在启用-XX:+UseCompressedOops(默认开启)前提下,为平衡内存占用、CPU缓存行利用率和字段寻址效率做出的硬性工程选择。
对象头:Mark Word + Klass Pointer 是两个物理连续但语义独立的块
对象头固定占12字节(不是8或16),拆成两段看才不会误判:
-
Mark Word始终占8字节:存储锁状态、GC年龄、哈希码等运行时元信息;它的二进制内容随对象状态动态复用,比如轻量级锁膨胀后,原本存哈希的位置就变成指向Monitor的指针 -
Klass Pointer占4字节(指针压缩开启时):指向方法区中的instanceKlass结构;若关闭压缩(-XX:-UseCompressedOops),它会涨到8字节,直接导致所有对象大小+4字节,且可能触发更多Humongous Allocation - 注意:JOL输出里
0 8 (object header : mark)和8 4 (object header : class)是紧挨着的,但二者功能完全解耦——你不能把它们合起来当成一个“12字节头”去算偏移,尤其在用Unsafe做字段偏移计算时,必须分开处理
实例数据不是按源码顺序排的,而是按宽度重排再填充
JVM不保证int a; long b;在内存里就是a在前、b在后。它会主动重排字段来减少对齐浪费:
- 优先排宽字段:
long、double(8字节)会被提到最前面,接着是int、float(4字节),最后才是byte、boolean(1字节) - 父类字段一定在子类字段之前:哪怕子类有个
long而父类只有int,父类int仍占偏移12位置,子类long从16开始,中间可能插4字节填充 - 引用类型字段大小取决于压缩开关:
String str在UseCompressedOops下占4字节,关掉就占8字节——这意味着改一个JVM参数,整个类的字段偏移全变,JNI或Unsafe.objectFieldOffset()结果全部失效
对齐填充不是“补零”,而是强制让对象总大小是8的倍数
对齐填充(padding)只发生在对象末尾,且只为满足“8字节对齐”这一条规则:
- 一个空
Object:对象头12字节 → 补4字节 → 总16字节(Instance size: 16) - 含一个
int的类:对象头12 + int 4 = 16 → 刚好对齐 → 无填充 → 总16字节 - 含一个
byte的类:对象头12 + byte 1 = 13 → 补3字节 → 总16字节 - 含两个
long的类:对象头12 + long×2(16)= 28 → 补4字节 → 总32字节 - 关键点:填充字节不参与GC扫描,也不影响字段访问;但它真实占用堆空间,并推高整体内存压力——高频创建小对象时,这4–7字节的“隐形开销”会快速累积
真正容易被忽略的是:指针压缩不是全局开关,而是与堆大小强绑定的隐式策略——当-Xmx设为32GB时,UseCompressedOops仍有效;但设为33GB,JVM会静默关闭它,且不报错,只在java -XX:+PrintFlagsFinal里显示UseCompressedOops := false。这时候所有对象突然变大,GC频率上升,而你可能还在盯着Metaspace查OOM。











