一、对象的内存布局

在JVM中,对象主要是存储在堆内存的,其中分为三个部分:对象头、实例数据和对齐填充。

对象头

 

对象头包括两类信息。

第一类:Mark Word。用于存储对象自身的运行时数据,如哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等。这部分的数据长度在32位和64位的虚拟机中分别是32byte和64byte。并且这一部分数据官方成为Mark Word。Mark word的存储内容会随着对象的状态变化而改变。已32位虚拟机为例,对象在无锁状态下,有25比特用于存储对象哈希码,4比特存储分代年龄,2比特存储锁标志位,1比特固定为0,基本上64位虚拟机中也是4比特存储分代年龄,2比特存储标志位。

在对象占用锁的状态下,如偏向锁中对象头会存放当前线程id,锁标志位为01;在轻量级锁的情况下会存放指向栈中锁记录的指针,锁标志位为00;在重量级锁情况下会存储指向互斥量的指针,锁标志位为10。

第二类:类型指针。对象指向它的类型元数据的指针。需要注意的是并不是所有的对象会在对象头中存储类型指针。如果对象是一个数组,对象头中必须有一块用于记录数组长度的数据。

实例数据

实例数据即正在属于对象本身的一些数据,包括我们定义的各种类型的字段,同时包含父类中的字段内容。这部分的存储顺序会受到虚拟机分配策略参数(-XX:FieldsAllocationStyle参数)和字段在Java源码中定义顺序的影响。HotSpot虚拟机默认的分配顺序为longs/doubles、ints、shorts/chars、bytes/booleans、oops,相同宽度的字段总是被分配在一起存放,并且父类中定义的变量会出现在子类的之前。可以通过+XX:CompactFields设定为true,以允许子类中较窄的变量插入父类变量的空隙中以节省空间。

对象填充

此部分仅仅作占位符的作用。因为HotSpot虚拟机的自动内存管理系统要求对象起始地址必须是8字节的整数倍,当对象头的数据不够8字节的整数倍的时候需要通过对齐填充来补全。

二、对象的访问定位

访问定位即栈中的对象引用定位到对象的方式。根据不同虚拟机的不同具体实现而定,主流的访问方式有两种:句柄和直接指针。

句柄访问

相对于直接指针,也就是简介指针。使用句柄访问的话,虚拟机会在Java堆中划分出一块区域用作句柄池,栈中的对象引用(reference)存储的就是对象的句柄地址,句柄地址对应的区域存储着指向对象实例数据的指针和指向类型数据的指针。然后通过句柄池的指针找到对应的实例数据和类型数据。

直接指针

直接指针则是对象引用直接指向对象实例数据地址,而对应类型数据则直接通过对象实例数据中存储的类型数据地址指针找到即可。

使用直接指针的优势就是速度相对较快,而使用句柄访问的优势则是更加稳定,句柄地址正常来说是稳定不变的,当对象被移动,对象地址改变时,无需改变栈中对象引用地址,只需改变句柄中的对象实例数据指针即可。