关于HotSpot对象内存布局

383 阅读3分钟

「这是我参与2022首次更文挑战的第1天,活动详情查看:2022首次更文挑战

对象组成

HotSpot虚拟机中(为什么要特指HotSpot虚拟机呢?因为对象在不同的虚拟机中组成是存在区别的),对象在堆内存中的存储布局可以划分为三个部分。

  • 对象头(header
  • 实例数据(Instance Data
  • 对齐填充(Padding

image.png

对象头

对象头包含两部分信息:Mark Word 和 klass pointer (类型指针),如果该对象为数组对象,那么还有包含 Length (用来记录数组长度)。

Mark Word

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

在运行期间 Mark Word 存储的数据会随着锁标记位的变化而变化。

组成


image.png

  • 锁标志位(lock) :区分锁状态,11时表示对象待GC回收状态, 只有最后2位锁标识(11)有效

  • 偏向锁标记(biased_lock) :是否偏向锁,由于无锁和偏向锁的锁标识都是 01,没办法区分,这里引入一位的偏向锁标识位。

  • 分代年龄(age) :表示对象被GC的次数,当该次数到达阈值的时候,对象就会转移到老年代。

由于只给了 age 四个bit的空间,所以阈值最高只能为15,多了没地方存。

  • 对象的hashcode(hash) :运行期间调用 System.identityHashCode() 来计算(采用延迟计算的方式),并把结果赋值到这里;当处于重量级锁状态下,hashcode会转移到 Monitor 中

  • 偏向锁的线程ID(thread) :偏向模式的时候,当某个线程持有对象的时候,对象这里就会被置为该线程的ID。 在后面的操作中,就无需再进行尝试获取锁的动作。

  • epoch:验证偏向锁有效性的时间戳

  • ptr_to_lock_record:轻量级锁状态下,指向栈中锁记录(lock_record)的指针

  • ptr_to_heavyweight_monitor:重量级锁状态下,指向对象监视器Monitor的指针。

klass pointer

类型指针,即对象指向它类型元数据的指针,jvm通过这个指针来确定该对象是哪个类的实例;如果对象是一个数组,那么在对象头中还必须有一块用于记录数组长度的数据,这样jvm就可以通过普通java对象的元数据信息来确定对象的大小,但如果长度不确定,将无法通过对象头信息推断出数组的大小;

什么意思呢?你有一个Persion实例的引用,那么就可以通过它来找到元空间中相应的元数据了。

image.png

实例数据

对象真正存储的有效信息,即我们在类中定义的各种类型的字段内容,无论是继承的,还是在子类中定义的字段都必须记录起来。

对齐填充

没有啥特别的意义,仅仅起着占位符的作用。由于 HotSpot 虚拟机的自动内存管理系统要求对象起始地址 必须是8字节的整数倍,也就是对象的大小必须是8字节的整数倍,因此如果对象的实例数据部分没有对齐的话,就需要通过对齐填充来补全,达到8字节的整数倍要求。

示例

引入该包可以打印出对象组成

<dependency>
    <groupId>org.openjdk.jol</groupId>
    <artifactId >jol-core</artifactId>
    <version>0.14</version>
</dependency>

测试

public static void main(String[] args)  {
      User o = new User();
      o.setName("cw");
      o.setId(1);
System.out.println(ClassLayout.parseInstance(o).toPrintable());
    }

对象赋值状态下

image.png

看一下 Mark Work 里面是怎么对应的,这玩意在win系统下得倒着看

00000001 00000000 00000000 00000000

00000000 00000000 00000000 00000000

从后往前看,

黄色为 unused

浅绿色为对象的 hashCode

橙色为 unused

紫色为分代年龄

湖蓝色为偏向锁标记

粉色为锁标志位