写在前面
Java是用C++写的,所以java对象最终会映射到c++中的某个对象,用这个对象可以描述所有Java对象。而我们所熟知的synchronized锁的优化就是基于这个对象来实现的。
对象在内存中的布局
Java对象在被创建的时候,在内存分配完成后,虚拟机需要对对象进行必要设置, 例如这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的GC分代年龄等信息。
这些信息存放在对象的对象头(Object Header)中。根据虚拟机当前运行状态的不同,如是否启用偏向锁等对象头会有不同的设置方式。
在虚拟机中,对象在内存中存储的布局可以分为3块区域:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)
对象头
HotSpot虚拟机对象头包括两部分信息,第一部分用于存储对象自身的运行时数据,如哈希码、GC分代年、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳。
这部分数据的长度在32位和64位的虚拟机中分别是32bit和64bit,官方称为"Mark Word"。 考虑到虚拟机的空间效率,Mark Word被设计成一个非固定的数据结构以便在极小的空间内存储尽量多的信息。
对象头另一部分是类型指针,即指向对象的类元数据,虚拟机通过这个指针确定该对象是哪个类的实例。
我们可以在JVM源码(hotspot/share/oops/markOop.hpp)中看到对象头中存储内容的定义
class markOopDesc: public oopDesc {
public:
enum {
age_bits = 4,
lock_bits = 2,
biased_lock_bits = 1,
max_hash_bits = BitsPerWord - age_bits - lock_bits - biased_lock_bits,
hash_bits = max_hash_bits > 31 ? 31 : max_hash_bits,
cms_bits = LP64_ONLY(1) NOT_LP64(0),
epoch_bits = 2
};
}
- hash: 对象的哈希码
- age: 对象的分代年龄
- biased_lock : 偏向锁标识位
- lock: 锁状态标识位
- JavaThread* : 持有偏向锁的线程ID
- epoch: 偏向时间戳
例如在32位的HotSpot虚拟机中,如果对象处于未被锁定的状态下,那么Mark Word的32bit空间的25bit用于存储对象哈希码,4bit用于存储对象分代年龄,2bit用于存储锁标志位,1bit固定为0
而在其他状态(轻量级锁,重量级锁,GC标记,可偏向)下对象的存储内容如下表所示
实例数据
实例数据部分是对象真正存储的有效信息,也是在程序代码中说定义的各种类型的字段内容。
对其填充
第三部分对其填充并不是必然存在的,也没有特别的含义,仅是占位符的作用,因为HotSpot VM的内存管理系统要求对象起始地址必须是8字节的整数倍,换句话说,就是对象的大小必须是8字节的整数倍。而对象头部分正好是8字节的倍数,因此当对象实例数据部分没有对齐时,就需要通过对齐填充来补全。
查看对象头
我们可以通过openjdk的jol工具来查看对象头存储的内容,首先代码如下
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.9</version>
</dependency>
public class GetRange {
// -XX:-UseCompressedOops
public