Advanced Java-05-对象内存结构

HotSpot 虚拟机中,对象在 Heap 中存储的布局可以分为三块区域:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)。

对象头

在HotSpot里对象头包括3部分(如果此对象不是数组,则只包括前2部分):

  1. Mark Word: 用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等,这部分数据的长度在32位和64位VM中分别为32bit和64bit;
  2. Class Metadata Address: klass类型指针, 用来指向对象对应的Class对象(其对应的元数据对象)的内存地址。这部分数据的长度在32位和64位VM中分别为32bit和64bit。64位开启指针压缩的情况下, 这部分占32bit;
  3. Array Length: 如果是数组对象,还需要有一个Array Length保存数组长度的空间,32bit

对象头

对象头中的 Mark Word

../_images/Advanced-Java.Object.MarkWord64.png

对于 64bit 的 JVM,Mark Word 的大小是 64bit(8 字节),根据对象的不同状态,对象的 Mark Word 存储的内容也不同:

  • tag=01:无锁状态下,保存的是对象 hashCode 和 GC 的分代年龄
  • tag=01、00、10:如果使用了该对象的同步锁(对该对象使用了 Synchronized 代码块),对象的 Mark Word 会存储锁相关的信息,这部分参考 @ref Java-并发.03.Synchronized
  • tag=11:GC 标记,例如 forwarding pointer 等 @ref Advanced-Java.03a.GC

如果对象调用过 hashCode 方法,计算出的 hash 值缓存在 Mark Word

Java GC详解 - 最全面的理解Java对象结构 - 对象指针 OOPs | HeapDump性能社区:

  1. Hashcode 尽量只计算一次,计算出后,对于无锁对象,保存在对象标记字 Markword 中。
  2. 处于各种锁状态的话(除了偏向锁),都会修改并占用 Markword 导致需要其他地方缓存计算好的 hashcode,对于重量锁是对应的 Monitor 中保存,对于轻量锁是所指向的锁记录的指针中保存。
  3. 对于偏向锁,由于没法哈希值,所以只要计算过哈希值,就不会再进入偏向锁的状态,而是直接从轻量锁开始。
  4. 对于 JDK 15 之后引入的异步 Monitor 降级(Async Monitor Deflation),需要在这个过程完成或者未开始的时候读取 monitor 对象的 hashcode 缓存。对于这个特性的详细说明,可以参考:Async Monitor Deflation

分代年龄,是该对象经历的 Young GC 次数,超过 -XX:MaxTenuringThreshold=n 的对象会被晋升至老年代,因为这个分代年龄只有 4bit,所以最大值是 15,上面提到的 MaxTenuringThreshold 不能超过 15(默认也是 15)// 关于对象的动态年龄计算,参考 Advanced-Java.03b.GC案例分析

GC 标记:使用带有“整理”功能的 GC,即 GC 前后对象地址可能发生变化,就需要这部分存储 “forwarding pointer” 参考 Advanced-Java.03a.GC /Copying GC 和 Mark-Compact

对象头中的 Class Pointer

指向对象实的 Class 的指针。Java 7 之前指向的区域位于持久代(Permanent Generation),Java 8 之后,持久代废弃,引入了元数据区的概念(Metaspace),所以 Java 8 之后指向的是这个元数据区。

这个指针可能是被压缩的,就是压缩指针(Compressed OOPs)。当开启对象压缩时占用4字节(JVM 默认开启),关闭时占用8字节。

如果字节对齐 -XX:ObjectAlignmentInBytes 设置为 8,那么堆超过 32G 时,压缩指针会失效;

@ref: Java GC详解 - 最全面的理解Java对象结构 - 对象指针 OOPs | HeapDump性能社区

实例数据

去掉对象头, 剩下的是实例数据(Instance Data)和对齐填充(Padding):
实例数据部分包括了对象的所有成员变量,其大小由各个成员变量的大小决定,比如:byte和boolean是1个字节,short和char是2个字节,int和float是4个字节,long和double是8个字节,reference是4个字节(64位系统中是8个字节)。
非final非static成员属性才在这里, final的常量属性在方法区; static的属性在class对象里, class对象也在堆区.

对齐填充

填充字节, 使得对象的大小是8的倍数