JAVA 对象内存布局

引言

在 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


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值