Follow me on GitHub

对象的内存布局

对象存储在 上,这块内存可以细分为 3 部分:

  1. 对象头(Header)
  2. 实例数据(Instance Data)
  3. 对齐填充(Padding)

这就是对象的内存布局。

对象头

根据是普通对象还是数组,对象头分为:

  1. Mark Word
  2. 类型指针
  3. 数组长度

Mark Word

Mark Word 用于存储 对象自身的运行时数据,如:

  1. 对象的 hashCode
  2. GC 分代年龄
  3. 锁状态信息
  4. 线程持有的锁
  5. 偏向线程 ID
  6. 偏向时间戳

在 32 位和 64 位的虚拟机中,Mark Word 分别占用 32 字节和 64 字节,因此称其为 word。

Mark Word 有两个问题:

  1. 对象需要存储的 运行时数据很多,早就超过了 32 字节 or 64 字节所能记录的极限;
  2. Mark Word 存储的并非对象的 实际业务数据(如对象的字段值),属于 额外存储成本,占用的空间越小越好;

因此为了节约存储空间,Mark Word 被设计为一个 非固定的数据结构,以便在尽量小的空间中存储尽量多的数据,它会根据对象的状态,变换自己的数据结构,从而 复用 自己的存储空间,例如:

Mark Word

在 32 位 JVM 上,Mark Word 只有 32 字节,根据 不同的锁状态,却有 5 种不同数据结构,相当于在 同一份内存中 存储了 5 种不同信息,考虑到 Java 中一切皆对象,这种设计可以大大节约内存空间。

类型指针

类型指针即指向该对象所属类的元数据的指针,类的元数据存储在 方法区,因此类型指针指向方法区的一块内存。JVM 通过类型指针来确定该对象是哪个类的实例。

数组长度

对于普通对象,JVM 可以通过其类型的元数据信息 确定其大小,但对于数组对象,却无法仅通过数组的元数据确定数组对象的大小,因此需额外存储数组长度。

实例数据

实例数据是对象真正存储的 有效信息,也是 Java 代码中定义的各种类型的 字段内容,包括继承字段、本类定义的字段。

字段内容的存储顺序受两个因素影响:

  1. JVM 分配策略
  2. 父类继承字段 or 本类定义字段

HotSpot 默认的分配策略为:

  1. long & double
  2. int
  3. short & char
  4. byte & boolean
  5. oop(ordinary object pointer)

相同宽度的类型字段总是分配到一起,在满足默认分配策略的前提下,父类继承字段 先于 本类定义字段。

对齐填充

对齐填充只是占位符,不一定存在。

HotSpot 虚拟机要求对象的 起始地址 必须是 8 字节的整数倍,即 对象大小 必须是 8 字节的整数倍,而对象头必然是 8 字节的整数倍,所以当实例数据不满足该规定时,需要对齐填充来补全。