天天看点

Java 对象的内存布局

  1. 对象头

    对象头的信息主要包括两个部分:

Mark Word

类型指针

数组长度(如果是数组才有)

1.1 Mark Word

Mark Word 的定义:

Mark Word 用于存储对象自身的运行时数据,如哈希码(HashCode)、GC 分代年龄、锁状态标志、线程持有的锁、偏向线程ID 、偏向时间戳等。这部分数据的长度在32 位和64 位的虚拟机中分别为32 bit 和64 bit 。

问题?:如果对象需要存储的运行时数据有很多,超过了32位 或64 位Bitmap 结构所能记录的限度应该怎么办? (对象头是与对象本身无关的额外存储成本)

考虑到虚拟机的的空间效率,Mark Word 被设计成一个非固定的数据结构以便在极小的空间内存储尽可能多的信息,它会根据对象的状态复用自己的存储空间。

例如在32位 的虚拟机中,如果对象处于未被锁定的状态,32 bit 的分布:

对象哈希码:25 bit

GC 分代年龄:4 bit

锁标志位: 2 bit

1 bit 固定为 0

HotSpot 虚拟机对象头Mark Word

存储内容

标志位

状态

对象哈希码、对象分代年龄

01

未锁定

指向锁记录的指针

00

轻量级锁定

指向重量级锁的指针

10

膨胀(重量级锁定)

空,不需要记录信息

11

GC 标记

偏向线程ID 、 偏向时间戳、对象分代年龄

01

可偏向

标志位是可复用的。

1.2 类型指针

类型指针即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。

ps: 并不是所有的虚拟机实现都必须保留类型指针,或者说查找对象的元数据不一定通过类型指针。

就相当于reference 类型。

1.3 数组长度

如果对象是一个Java 数组,那在对象头中还必须有一块用于记录数组长度的数据,因为虚拟机可以通过普通Java 对象的元数据确定Java 对象的大小,但是从数组的元数据无法确定数组的大小。

2. 实例数据

实例数据是对象真正存储的有效信息: 代码中定义的各种类型的字段内容(包含父类继承的和子类定义的,都需要记录下来)。

这部分的存储顺序受到虚拟机分配策略(FieldsAllocationStyle)和字段在源码中的顺序的影响。

HostSpot 的默认分配策略为:

longs/doubles

ints

shorts/chars

bytes/booleans

oops(Ordinary Object Pointers)

可以看出:相同宽度的字段总是被分在一起。

另外,父类中定义的变量会出现在子类之前。

如果CompactFields 参数值为true(默认为true),那么子类之中较窄的变量也可能会插入父类变量的空隙之中。

3. 对齐填充

对齐填充在内存布局里面仅仅起到占位符的作用,并不是必然存在的,也没有特殊含义。

由于 HostSpot VM 的自动内存管理系统要求对象的起始地址必须是8字节的整数倍,换句话说对象的大小必须为8字节的整数倍,要是实例数据没有对齐,则需要进行对齐填充来补全。

Java 对象的内存布局

继续阅读