Java对象内存布局
2024-02-01 15:22:12 # Language # Java

1. 对象的内存布局

在 HotSpot 虚拟机里,对象在堆内存中的存储布局可以划分为三个部分:对象头(Header)、实例数据(Instance Data) 和对齐填充(Padding)

img

1.1 对象头

在64位系统中,Mark Word占了8个字节,类型指针占了8个字节,一共是16个字节

对象标记(Mark Word)

存储对象自身的运行时数据,如

  • HashCode、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等

  • 这部分数据长度在 32 位和 64 位的虚拟机(未开启压缩指针)中分别为 32 个比特和 64 个比特

  • 这些信息都是与对象自身定义无关的数据,所以Mark Word被设计成一个非固定的数据结构以便在极小的空间内存存储尽量多的数据。
  • 它会根据对象的状态复用自己的存储空间,也就是说在运行期间MarkWord里存储的数据会随着锁标志位的变化而变化。

image-20240131140355382

img

对象分代年龄最大值为15,即(1111)

类元信息(类型指针)

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

指向方法区中类的 Klass 类元信息

压缩指针

  • java -XX:+PrintCommandLineFlags -version 可查看虚拟机开启了哪些参数配置
    • -XX:+UseCompressedClassPointers 默认开启压缩指针,只占用 4 字节
    • -XX:-UseCompressedClassPointers 关闭压缩指针

1.2 实例数据

存放类的属性(Field)数据信息,包括父类的属性信息

1.3 对齐填充

虚拟机要求对象起始地址必须是8字节的整数倍,填充数据不是必须存在的,仅仅是为了字节对齐,这部分内存按8字节补充对齐。

2. JOL

引入依赖

1
2
3
4
5
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.17</version>
</dependency>

测试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Object o = new Object();
System.out.println(ClassLayout.parseInstance(o).toPrintable());
/*
java.lang.Object object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x0000000000000001 (non-biasable; age: 0)
8 4 (object header: class) 0xf80001e5
12 4 (object alignment gap)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
*/

Customer c = new Customer();
System.out.println(ClassLayout.parseInstance(c).toPrintable());
/*
top.whale.Customer object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x0000000000000001 (non-biasable; age: 0)
8 4 (object header: class) 0xf800c143
12 4 int Customer.id 0
16 1 boolean Customer.flag false
17 7 (object alignment gap)
Instance size: 24 bytes
Space losses: 0 bytes internal + 7 bytes external = 7 bytes total
*/