「这是我参与2022首次更文挑战的第1天,活动详情查看:2022首次更文挑战」
对象组成
在HotSpot虚拟机中(为什么要特指HotSpot虚拟机呢?因为对象在不同的虚拟机中组成是存在区别的),对象在堆内存中的存储布局可以划分为三个部分。
- 对象头(
header) - 实例数据(
Instance Data) - 对齐填充(
Padding)
对象头
对象头包含两部分信息:Mark Word 和 klass pointer (类型指针),如果该对象为数组对象,那么还有包含 Length (用来记录数组长度)。
Mark Word
用于存储对象自身的运行时数据,如哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向锁ID、偏向时间戳等
在运行期间 Mark Word 存储的数据会随着锁标记位的变化而变化。
组成
-
锁标志位(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实例的引用,那么就可以通过它来找到元空间中相应的元数据了。
实例数据
对象真正存储的有效信息,即我们在类中定义的各种类型的字段内容,无论是继承的,还是在子类中定义的字段都必须记录起来。
对齐填充
没有啥特别的意义,仅仅起着占位符的作用。由于 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());
}
对象赋值状态下
看一下 Mark Work 里面是怎么对应的,这玩意在win系统下得倒着看
00000001 00000000 00000000 00000000
00000000 00000000 00000000 00000000
从后往前看,
黄色为 unused
浅绿色为对象的 hashCode
橙色为 unused
紫色为分代年龄
湖蓝色为偏向锁标记
粉色为锁标志位