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
Explore the Jvm source code implementation

浙公网安备 33010602011771号