在HotSpot虚拟机里,对象的存储布局分为以下三部分。
-
对象头(Header)中存储的信息类似于计算机网络中的IP数据报的首部,是对对象的一些附加说明标志的信息,与对象的自身定义的数据无关,下边会详细说明。
-
实例数据(Instance Data)中存储的就是对象真实有效的信息。
-
对齐填充(Padding)部分没有特别的含义,单纯为了满足虚拟机规定的“对象起始地址必须为8字节的整数倍”的要求而设置。
一.对象头
对象头中包含两部分信息,第一部分为存储对象自身运行时的数据,另一部分为类型指针,如果对象是一个数组,那么对象头还需要有额外的空间用于存储数组的长度。
第一部分又称为“Mark Word”,由于这部分是额外的存储成本,所以为了充分利用这部分的空间,设计者根据对象不同的状态将这部分存储不同的信息,即复用存储空间。不同的状态是根据Mark Word中的标志位区分的,下表即根据状态划分的存储内容。
存储内容 | 标志位 | 状态 |
---|---|---|
对象哈希码、对象分代年龄 | 010 | 未锁定 |
指向锁记录的指针 | 00 | 轻量级锁定 |
指向重量级锁的指针 | 10 | 膨胀(重量级锁定) |
空,不需要记录信息 | 11 | GC标记 |
偏向线程ID、偏向时间戳、对象的分代年龄 | 101 | 可偏向 |
下表为32位Mark Word的详细分布:
GC就是垃圾回收状态,其他为对象的锁状态。在这里简单引申一下锁的概念 ,在多线程的情况下,锁在线程安全中扮演着举重轻重的作用。锁分为乐观锁和悲观锁。在java SE1.6中,锁有四种状态,从低到高分别是无锁状态,偏向锁状态,轻量级锁状态,重量级锁,这几个状态会随着竞争状况逐渐升级,锁的级别越高意味着线程对此对象的竞争也就越大。
第二部分类型指针,这个指针就是确定这个对象是哪个类的实例。 但是书中说这个不是必须的,需要对应对象的访问方式
存在的意义在于,栈中对象的引用使用直接指针的时候,该指针指向堆内存中的对象,所以对象头是需要存储它的类云数据指针,这个指针才是指向方法区中对象的类型 数据。
第三部分存储数组的长度部分的长度也随着JVM架构的不同而不同:32位的JVM上,长度为32位;64位JVM则为64位。64位JVM如果开启+UseCompressedOops
选项,该区域长度也将由64位压缩至32位。
二.实例数据
这部分存储的就是这个对象的真实数据了,也就是程序代码中定义的字段内容,当然也包括从父类继承下来的字段。而这些信息的存储顺序取决于虚拟机的分配策略。下面介绍虚拟机的分配策略。
基本规则:
1.存储顺序见下表:默认基本类型在前,按长度由长至短存储,然后就是对象指针引用。相同宽度的字段分配到一起。如果设置了-XX:FieldsAllocationStyle=0(默认是1),那么引用就会放在最前面。
2.对于同宽度的字段,父类在前子类在后。如果开启CompactFields,那么,子类中较窄的变量就会插到父列的变量的空隙中。
三、填充部分
对齐补充不是必须存在的,就是占位的作用。 由于HotSpot VM的自动内存管理系统要求对象起始地址必须是8字节的整数倍,这时若实例数据部分没有对齐时,就需要通过对齐填充来补全。