HotSpot JVM 中 Java 对象的内存布局

Ordinary Object Pointers (OOPs)

在 HotSpot JVM 中使用一些类名为 {name}Desc 的类来描述 Java 对象(就是描述了 Java 对象的构成),这些类都继承了 oopDesc 基类; 并且这些类都叫做普通对象指针,简称 OOPs。

oopDesc 类中不仅包含了一个 Mark word 和一个 klass word(klass word、Class pointer 和 Class word 都是一样的);还有一个重要东西 static BarrierSet* _bs; 提供了屏障实现和系统其它部分之间的接口。

除属性相关的方法外,oopDesc 定义了如下几类方法:

  • 根据偏移量获取不同类型的 Java 字段的地址,如 byte_field_addr,int_field_addr,obj_field_addr 等
  • 指针压缩和解压缩的方法,如 decode_heap_oop,encode_heap_oop 等
  • 加载存储堆外对象的方法,如 load_heap_oop,store_heap_oop 等,堆外对象应该是指元空间中的对象
  • 根据偏移量获取和设置不同类型的 Java 字段的方法,如 byte_field,byte_field_put 等
  • 跟 GC 相关的方法,如 age,incr_age,is_gc_marked 等

Mark word

Mark word 是用来描述对象的头,在 HotSpot JVM 中是通过 markOopDesc 这个类实现的,在该类中有一个 uintptr_t 类型的变量并使用该变量来存储 identity hashcode,biased locking pattern,locking information and GC metadata。

注意:markOopDesc 它并不是一个 oop,因为历史原因才叫这个名字。

uintptr_t 类型在 32 位的系统中占用 4 个字节:

在 64 位的系统中占用 8 个字节:

markOopDesc 还提供了根据对象头获取对象状态信息的方法,比如 is_locked 方法判断对象是否上锁,通过 has_monitor 方法判断对象是否有对象锁,还有就是可以获取 ObjectMonitor(实现了 JavaMonitors)。

InstanceOopDesc

instanceOopDesc 继承自 oopDesc,用于表示 Java 对象(包含自定义类),每次 new 一个 Java 对象就会创建一个新的 instanceOopDesc 实例;例如:new HashTable() 就对应着一个 instanceOopDesc。

基本类型字段的保存是通过 instanceOopDesc 自身的地址,加上偏移量(单位是字节)计算出各个字段的地址,根据地址就可以获取或设置字段的值。

注意:各字段的偏移量和初始值等属性都保存在 InstanceKlass 的 _fields 属性中。

arrayOopDesc

arrayOopDesc 继承自 oopDesc,该类是所有数组 OopDesc 的基类,除 OopDesc 定义的属性外还需保存数组的长度,数组元素的取值。

该类添加了几个跟数组相关的方法,如下:

  • int base_offset_in_bytes(BasicType type):获取首元素的偏移量
  • void* base(BasicType type): 获取首元素的地址
  • bool is_within_bounds(int index):index是否数组越界
  • int length():获取数组的长度
  • void set_length(int length): 设置数组长度
  • int32_t max_array_length(BasicType type): 获取数组的最大长度

该类下面有两个实现:

  • typeArrayOopDesc:是一个包含基本类型(characters, singles, doubles, bytes, shorts, integers, longs)的数组。
  • objArrayOopDesc:是一个包含 OOPs 的数组;例如:String arg[10]。

klass word

klass word 是使用 Klass 类实现,该类描述了 Java 类的继承关系(子类、父类)和类的信息(类名、修饰符、ClassLoader),该类有两个实现类:ArrayKlass 和 InstanceKlass。

InstanceKlass

InstanceKlass 是 Java Class 在 JVM 层面的内存表示,包含了类的一些信息,例如:该类使用的注解、该类的常量池、该类的方法指针数组、该类的字段数组。

ArrayKlass

ArrayKlass 继承自 Klass,是所有数组类的抽象基类,可以记录数组维度,数组元素类型。

下面有两个实现:

  • ObjArrayKlass 是 ArrayKlass 的子类,用于表示元素是类的数组或者多维数组。
  • TypeArrayKlass 是 ArrayKlass 的子类,用于表示元素是基本类型如 int 的数组。

总结

这篇文章一般不会用到,也就是过面试题刷一刷。主要就是记住 Mark word 就可以了,剩下的记住一个大概我觉得就没问题了。

下面就是大概的总结:

  • Object Header:该区域中存储的信息分为两部分。

    • 对象本身的运行时数据(MarkWord):该数据存储了 hashCode、垃圾回收(GC)生成年龄、锁类型标志、偏向锁线程的 ID、指向 LockRecord 的比较和交换(CAS)锁指针的线程。同步锁的机制与这部分(MarkWord)高度相关。MarkWord 的最低三位代表锁状态。对于这三个位,其中一个是偏置锁定位,另外两个是普通锁定位。
    • 对象的类指针(Class Pointer):对象的指针是指向其类元数据的指针。JVM 使用它来确定实例的类。
  • 实例数据区
    该区域存储对象的有效信息,例如对象中所有字段的内容。

  • 对齐和填充区域
    JVM HotSpot 的实现规定对象的起始地址必须是 8 字节的整数倍。换句话说,64 位操作系统一次读取 64 位整数倍的数据,即 8 个字节。因此,HotSpot 执行“对齐”以有效读取对象。如果对象的实际内存大小不是 8 字节的整数倍,HotSpot 会“填充”对象为 8 字节的整数倍。因此,对齐和填充区域的大小是动态的。

拓展知识点——对象头的压缩

压缩后的 Mark word,这个是个重点,记住这个就可以了。

参考资料

Let’s Talk about Several of the JVM-level Locks in Java

ObjectHeader32.txt

Java object header

Explore the Jvm source code implementation

JVM-a simple understanding of the OOP-Klass binary mode

Hotspot Oop模型——Java对象内存表示机制

Hotspot Klass模型——Java类内存表示机制

CompressedOops

posted @ 2021-06-03 10:37  代号-G  阅读(330)  评论(0)    收藏  举报