对象的内存布局

HotSpot虚拟机中,对象在堆中的存储布局可以划分为三个部分

  • 对象头(Header
    • Mark Work
    • 类型指针
    • 数组长度(如果对象是数组)
  • 实例数据(Instance Data
  • 对齐填充(Padding

对象头

HotSpot虚拟机对象的对象头部分包括两类信息。

Mark Work

一类是用于存储对象自身的运行时数据,如哈希码,GC分代年龄,锁状态、线程持有的锁、偏向线程ID、偏向时间戳等。

这部分数据的长度在32位和64位的虚拟机中分别占用32和64个比特,官方称为Mark Work

为了存储的空间效率,Mark Work被设计成一个有着动态定义的数据结构,以便在极小的空间存储尽量多的数据。

其存储情况如下:

存储内容 标志位 状态
对象哈希码、对象分代年龄 01 未锁定
指向锁记录的指针 00 轻量级锁定
指向重量级锁的指针 10 重量级锁定(锁膨胀)
空、不需要记录信息 11 GC标记
偏向线程ID、偏向时间戳、对象分代年龄 01 可偏向

32位虚拟机占用4个字节,64位虚拟机占8个字节。

类型指针

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

并不是所有虚拟机的实现都必须在对象数据上保留类型指针。

32位虚拟机或64为虚拟机(-XX:+UseCompressedOops,开启指针压缩,默认开启)占用4个字节。

64为虚拟机(无指针压缩)占8个字节。

数组长度

如果对象是数组,额外需要占用4个字节。


实例数据

实例数据是对象真正存储的有效信息,即代码中定义的各种类型的字段内容。

类型 大小(字节)
byte 1
boolean 1
char 2
short 2
int 4
float 4
long 8
double 8
ref 32位或64位虚拟机(开启指针压缩)引用类型占用4个字节,
64位虚拟机引用类型占用8个字节。
字段的存储顺序

受虚拟机分配策略参数(-XX:FieldsAllocationStyle)和字段在源码中的定义顺序影响。

默认的顺序:

longs/doubles、ints、shorts/chars、bytes/booleans、oops

相同宽度的总被分配在一起,在满足这个前提的情况下,父类的变量会出现在子类之前。

如果XX:FieldsAllocationStyle=0(默认是1),那么oops会在最前边。

如果HotSpot虚拟机的+XX:CompactFields参数为true(默认true),那么子类之中较

窄的变量也允许插入到父类变量的空隙之中。


对齐填充

由于HotSpot虚拟机的自动内存管理系统要求对象起始地址必须是8字节的整数倍,

也就是任何对象的大小必须是8字节的整数倍。

查看对象内存布局

可利用jol查看对象内部布局,注意大小端。

public static void main(String[] args) throws InterruptedException {
    /*
     jvm 默认开启偏向锁(-XX:+UseBiasedLocking)
     在jvm启动后4s开启(-XX:BiasedLockingStartupDelay=4000),会看到101
     TimeUnit.SECONDS.sleep(5);
    */

    Object obj = new Object();
    System.out.println(ClassLayout.parseInstance(obj).toPrintable());

    // 加锁之后可以看到对象头的锁标识发生改变
    synchronized (obj) {
        System.out.println(ClassLayout.parseInstance(obj).toPrintable());
    }
}
<dependency>
    <groupId>org.openjdk.jol</groupId>
    <artifactId>jol-core</artifactId>
    <version>0.11</version>
</dependency>