Java对象结构

173 阅读5分钟

Java对象结构

HotSpot 虚拟机里,Java 对象(Object 实例)在堆内存中的存储布局可以划分为三部分:对象头、实例数据、对齐填充。 image.png

对象头

Mark Word(标记字段):用于存储自身运行时的数据,例如 GC 标志位、哈希码、锁状态等信息,它是实现轻量级锁和偏向锁的关键。

Class Pointer(类对象指针):用于存放方法区Class对象的地址,虚拟机通过这个指针来确定这个对象是哪个类的实例。 image.png

Array Length(数组长度):如果对象是一个 Java 数组,那么此字段必须有,用于记录数组长度的数据。如果对象不是一个 Java 数组,那么此字段不存在,所以这是一个可选字段。

实例数据:对象体包含了对象的实例变量(成员变量)。用于成员属性值,包括父类的成员属性值。这部分内存按4字节对齐。

对齐填充:对齐字节并不是必然存在的,也没有特别的含义,它仅仅起着占位符的作用。用来保证Java对象在所占内存字节数为 8 的倍数(8N bytes)。HotSpot VM 的内存管理要求对象起始地址必须是 8 字节的整数倍。对象头本身是 8 的 倍数,当对象的实例变量数据不是 8 的倍数,便需要填充数据来保证 8 字节的对齐。

对象头三部分的字段长度

Mark WordClass PointerArray Length 等字段的长度,都与 JVM 的位数有关。所以在 32 位 JVM 虚拟机中,Mark WordClass Pointer 这两部分都是 32 位的;在 64 位 JVM 虚拟机中,Mark WordClass Pointer 这两部分都是 64 位的。

对于对象指针而言,如果 JVM 中对象数量过多,使用 64 位的指针将浪费大量内存。为了节约内存可以使用选项 +UseCompressedOops 开启指针压缩。Mark Word的位长度是不会受到Oop对象指针压缩选项的影响。如果开启UseCompressedOops选项,以下类型的指针将从64位压缩至32位:

  • Class对象的属性指针(即静态变量)。
  • Object对象的属性指针(即成员变量)。
  • 普通对象数组的元素指针。

当然,也不是所有的指针都会压缩,一些特殊类型的指针不会压缩,比如指向PermGen(永久代)的 Class 对象指针(JDK8中指向元空间的 Class 对象指针)、本地变量、堆栈元素、入参、返回值和 NULL指针等。

如果对象是一个数组,那么对象头还需要有额外的空间用于存储数组的长度(Array Length 字 段)。Array Length 字段的长度也随着 JVM 架构的不同而不同:32 位的 JVM 上,长度为 32 位; 64 位 JVM 则为 64 位。64 位 JVM 如果开启了 Oop 对象的指针压缩,Array Length 字段的长度也 将由64位压缩至32位。

Mark Word 字段结构图解

以64位为例 image.png

Java 内置锁的状态总共有四种,级别由低到高依次为:无锁偏向锁轻量级锁重量级锁

其实在 JDK 1.6 之前,Java 内置锁还是一个重量级锁,是一个效率比较低下的锁,在 JDK 1.6 之 后,JVM 为了提高锁的获取与释放效率,对synchronized的实现进行了优化,引入了偏向锁、轻量级锁的实现,从此以后Java内置锁的状态就有了四种(无锁、偏向锁、轻量级锁、重量级锁), 并且四种状态会随着竞争的情况逐渐升级,而且是不可逆的过程,即不可降级,也就是说只能进行锁升级(从低级别到高级别)。Mark word字段的结构,与Java内置锁的状态相关。为了让Mark word字段存储更多的信息,JVM 将 Mark word的最低两个位设置为 Java 内置锁状态位,不同锁状态下的 32 位 Mark Word 结构。

  • lock:锁状态标记位占 2 个二进制位,由于希望用尽可能少的二进制位表示尽可能多的信息,所以设置了lock标记。该标记的值不同,整个Mark Word表示的含义不同。
  • biased_lock对象是否启用偏向锁标记,只占 1 个二进制位。为 1 时表示对象启用偏向锁,为 0 时表示对象没有偏向锁。
  • lockbiased_lock 两个标记位组合在一起,共同表示Object实例处于什么样的锁状态。二者组合的含义如下图: image.png
  • age4位的 Java 对象分代年龄。在 GC 中,如果对象在 Survivor 区复制一次,年龄增加 1。当对象达到设定的阈值时,将会晋升到老年代。默认情况下,并行 GC 的年龄阈值为 15,并发 GC 的年龄阈值为 6。由于 age 只有 4 位,所以最大值为 15,这就是-XX:MaxTenuringThreshold 选 项最大值为 15 的原因。
  • identity_hashcode31 位的对象标识 HashCode(哈希码),采用延迟加载技术,当调Object.hashCode()方法或者System.identityHashCode()方法计算对象的HashCode后,其结果将被写到该对象头中。当对象被锁定时,该值会移动到Monitor(监视器)中。
  • thread54位的线程ID值,为持有偏向锁的线程ID。
  • epoch偏向时间戳。
  • ptr_to_lock_record占 62 位,在轻量级锁的状态下,指向栈帧中锁记录的指针。
  • ptr_to_heavyweight_monitor占 62 位,在重量级锁的状态下,指向对象监视器Monitor的指针。