Java对象内存布局由对象头(Mark Word和Class Pointer)、实例数据区(字段重排序)和对齐填充三部分组成;数组对象额外包含4字节length字段;对象大小需满足8字节对齐,最小为16字节。

Java对象在堆内存中不是简单的一块连续数据,而是有固定结构的布局,直接关系到对象大小、对齐、GC行为和性能优化。
对象头包含哪几部分
每个Java对象头部固定包含两部分:Mark Word 和 Class Pointer。64位JVM默认开启指针压缩(-XX:+UseCompressedClassPointers)时,Class Pointer占4字节;未开启则占8字节。Mark Word在不同状态下复用字段,如存储哈希码、GC分代年龄、锁状态(无锁/偏向/轻量级/重量级)、线程ID等——这些信息动态变化,不占用额外空间。
注意:java.lang.Object 实例本身没有实例字段,但仍有对象头,因此其最小内存占用不为0。
实例数据区按字段顺序和宽度排列
字段不是按源码顺序原样排列,而是遵循JVM的字段重排序规则:从宽到窄(long/double → int/float → char/short → byte/boolean),同一宽度内才按声明顺序排列。这样能减少填充(padding)字节。
立即学习“Java免费学习笔记(深入)”;
- 父类字段排在子类字段之前
- 静态字段(
static)不参与对象布局,存在方法区(或元空间) - 引用类型字段在开启指针压缩时占4字节,否则占8字节
class A {
byte b; // 1字节
int i; // 4字节
long l; // 8字节 → 实际布局:l(8) + i(4) + b(1) + padding(3)
}
对齐填充不是可选,而是强制要求
JVM要求每个对象起始地址必须是8字节的整数倍(即满足objectAddress % 8 == 0)。如果对象头+实例数据总长度不是8的倍数,虚拟机会在末尾自动添加填充字节(padding)补足。这意味着即使你只定义了一个byte字段,对象大小也至少是16字节(对象头12字节 + 字段1字节 + 填充3字节 = 16字节)。
验证方式:使用 Instrumentation.getObjectSize() 或 JOL(Java Object Layout)工具,例如:
System.out.println(ClassLayout.parseInstance(new Object()).toPrintable());
输出中会明确标出 gap(填充)区域。
数组对象多一个4字节的length字段
所有数组(包括基本类型数组和对象数组)都是继承自Object,但多了独立的length字段,固定占4字节,放在实例数据区最前面(紧接对象头之后)。这也是为什么new int[0]仍占16字节(对象头12 + length 4 = 16),而new Integer[0]同样占16字节——引用数组的元素部分为空,不占空间。
注意:length字段不可被继承或覆盖,且JVM直接通过数组对象头偏移访问它,不走普通字段查找逻辑。
真正影响对象大小的从来不是“写了几个字段”,而是字段类型组合、是否启用指针压缩、以及JVM版本对对象头的实现细节(比如ZGC或Shenandoah可能略有差异)。别依赖直觉估算,遇到内存敏感场景,务必用JOL实测。










