Java对象在堆内存中由对象头(含Mark Word和Class Pointer)、实例数据(按字段宽度降序排列)和对齐填充(Padding)三部分组成。

Java对象在堆内存中由哪几部分组成
Java对象在堆内存中不是一块连续的“裸数据”,而是有固定结构的布局,主要包括三块:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)。这个结构由JVM规范约定,但具体实现(如HotSpot)有细节差异。
对象头又分为两部分:Mark Word(存储哈希码、GC分代年龄、锁状态等)和Class Pointer(指向该对象所属Class元数据的指针)。32位JVM下对象头通常8字节,64位默认12字节(开启指针压缩后也是12字节)。
- 开启指针压缩(
-XX:+UseCompressedOops,默认开启)时,Class Pointer占4字节;否则占8字节 -
Mark Word在无锁状态下存哈希码(若未调用hashCode()则为0)、分代年龄(4位)、偏向锁标记等,共8字节(64位平台) - 实例数据按字段宽度从大到小排列(
long/double→int/float→char/short→byte/boolean),以提升内存访问效率
为什么new出来的对象大小不等于字段大小之和
因为JVM要求对象起始地址必须是8字节的整数倍(即对象总大小需对齐到8字节边界),所以会在实例数据末尾补上若干字节的Padding。这是唯一原因——不是为了缓存行对齐,也不是为了GC效率,纯粹是HotSpot的内存分配策略要求。
例如:class A { byte a; },字段只占1字节,但对象实际占用16字节(对象头12字节 + 实例数据1字节 + 填充3字节 = 16字节)。
立即学习“Java免费学习笔记(深入)”;
- 用
Unsafe.objectFieldOffset()或JOL(Java Object Layout)工具可验证真实布局 - 数组对象额外多4字节长度字段(
int类型),放在对象头之后、元素数据之前 - 子类字段不会插入到父类字段中间,而是全部排在父类字段之后
对象头里的Mark Word内容会动态变化
Mark Word不是只读元数据,而是一个复用程度极高的“多用途字段”。它的语义取决于当前锁状态和是否调用过hashCode(),不同状态下各比特位含义完全不同。
- 无锁状态且未调用
hashCode():低3位为001(表示无锁),高31位可存identity hashcode(延迟计算) - 调用过
System.identityHashCode()或Object.hashCode():hashcode被写入Mark Word,后续不再重新计算 - 轻量级锁:存储指向栈中锁记录的指针
- 重量级锁:存储指向
Monitor对象的指针 - GC标记阶段:可能被替换成GC使用的标记位(如CMS中的Promotion Failure标记)
使用JOL查看真实对象布局的实操要点
别依赖IDE的“sizeof”插件或手动加总字段,直接用org.openjdk.jol:jol-core输出权威结果。它能反映当前JVM参数下的真实内存布局,包括是否开启指针压缩、是否启用压缩类指针等。
示例代码:
public class LayoutDemo {
public static void main(String[] args) {
System.out.println(VM.current().details());
System.out.println(ClassLayout.parseInstance(new Object()).toPrintable());
System.out.println(ClassLayout.parseInstance(new int[0]).toPrintable());
}
}
- 必须在目标JVM参数下运行(如
-XX:+UseCompressedOops),否则结果不具参考性 -
ClassLayout.parseInstance()只能分析已创建的对象,不能分析类定义本身 - 注意区分
shallowSize(对象自身占用)和retainedSize(含引用对象的总大小)
对象布局细节高度依赖JVM实现和启动参数,同一段代码在不同JDK版本或GC算法下可能有差异,尤其涉及锁膨胀、ZGC的染色指针等场景时,Mark Word结构可能被彻底重定义。










