引言
在 HotSpot 虚拟机中,创建一个对象后,该对象在堆内存中的存储布局划分为三个部分:对象头、实例数据和对齐补充。 本篇文章主要以创建对象后,对象在堆内存中的存储布局为主,想要了解创建对象的过程可以先去看这篇文章 从 JVM 虚拟机角度去看一个对象的创建过程。 另外还需要补充的是,如果本篇文章中有讲的不对的地方,可以直接私我或在下方评论,感谢!
一、对象头(Header)
当我们创建对象后,虚拟机堆中对象的对象头中包括了两类信息:
第一类:运行时数据
第一类是用于存储自身的运行时数据,比如说,哈希码( HashCode )、GC 分代年龄、锁状态标志、线程持有的锁、偏向线程 ID、偏向时间戳等,这部分的数据长度在 32 位和 64 位的虚拟机中(未开启 压缩指针)分别占用 32 个比特和 64 个比特,官方称它为 “Mark Work”。
压缩指针
刚刚上面提到了压缩指针,在这里我简单讲一下压缩指针: 在堆中,32 位的对象引用指针占 4 个字节,而 64 位的对象引用占 8 个字节。也就是说,64 位的对象引用大小是 32 位的 2 倍。64 位 JVM 在支持更大堆的同时,由于对象引用变大却带来了性能问题; 第一:64 位对象引用需要占用更多的堆空间,留给其他数据的空间将会减少,从而更加频繁的进行 GC,所以增加了 GC 开销。 第二:64 位对象引用增大了,CPU 能缓存的 OOP(普通对象指针) 将会更少,从而降低了 CPU 缓存的效率,所以降低 CPU 缓存命中率。 为了能够保持 32 位的性能,OOP(普通对象指针)必须保留 32 位,所以才要使用了 压缩指针 技术来保留 32 位;
接下来继续讲对象头: 其实一个对象需要存储的运行时数据很多,已经超出了 32、64 位 Bitmap 结构所能记录的最大限度,但对象头里的信息是与对象自身定义的数据无关的额外存储成本,考虑到虚拟机的空间效率,Mark Word 被设计成一个有着动态定义的数据结构,方便在极小的空间内存储尽量多的数据,根据对象的状态复用自己的存储空间。
比如说在 32 位的 HotSpot 虚拟机中,如果对象未被同步锁锁定的状态下,Mark Word 的 32 个比特存储空间中的 25 个比特用于存储对象哈希码,4 个比特用于存储对象分代年龄,2 个比特用于存储锁标志位,1 个比特固定为 0,在其他状态(轻量级锁定、重量级锁定、GC 标记、偏向锁定)下,这 1 个比特则变成 1。
第二类:类型指针
对象头的另外一部分是类型指针,就是对象指向它的类型元数据的指针,JAVA 虚拟机通过这个指针来确定该对象是哪个类的实例。 但是并不是所有的虚拟机实现都必须在对象数据上保留类型指针,换句话说,查找对象的元数据信息并不一定要经过对象本身。
此外,如果对象是一个 JAVA 数组,那在对象头中还必须有一块用于记录数组长度的数据,因为虚拟机可以通过普通 JAVA 对象的元数据信息确定 JAVA 对象的大小,但是如果数组的长度是不确定的,将无法通过元数据中的信息推断出数组的大小。
二、实例数据
实例数据部分是对象真正存储的有效信息,就是我们在程序代码里面所定义的各种类型的字段内容,无论是从父类继承下来的,还是在子类中定义的字段都必须记录起来。 这部分的存储顺序会受到 虚拟机分配策略参数(参数:-XX:FieldsAllocationStyle) 和字段在 JAVA 源码中定义顺序的影响。
内存分配策略
这里简单的扩充一下虚拟机内存分配策略,当我们创建对象时,大多数情况下,新创建的对象都会在堆内存中的新生代 Eden 区进行分配内存。 当 Eden 区没有足够的空间进行分配时,虚拟机会发起一次 Minor GC,如果本次 Minor GC 后还是没有足够的空间,则将启用分配担保机制在老年代中分配内存。
接下来继续讲实例数据: HotSpot虚拟机默认的分配顺序为:longs / doubles、ints、shorts / chars、bytes / booleans、oops(Ordinary Object Pointers,OOPs)(普通对象指针)。 从以上默认的分配策略中可以看到,相同宽度的字段总是被分配到一起存放,在满足这个前提条件的情况下,在父类中定义的变量会出现在子类之前。 如果HotSpot虚拟机的 +XX:CompactFields 参数值为true(默认就是 true),那子类之中较窄的变量也允许插入父类变量的空 隙之中,以节省出一点点空间。
三、对齐补充
对齐填充它仅仅起着占位符的作用,并不是必然存在的,也没有特别的含义。 由于 HotSpot 虚拟机的自动内存管理系统要求对象起始地址必须是 8 字节的整数倍,换句话说就是任何对象的大小都必须是 8 字节的整数倍。 对象头部分已经被精心设计成正好是 8 字节的倍数(1倍或者 2倍),因此,如果对象实例数据部分没有对齐的话,就需要通过对齐填充来补全。
本篇文章主要来源于《深入理解JAVA虚拟机》
End
|