1. 对象的内存布局
对象的内存布局主要包括:对象头(Header)、实例数据(Instance Data)和 对齐填充(Padding)。
1.1 对象头(Header)
对象头包括:MarkWord 和 和 类型指针。
MarkWord 是对象自身运行时候的对象头数据,例如:哈希码,GC分代年龄,锁状态标志,线程持有的锁,偏向线程 ID,偏向时间戳。这部分数据在 32 位 和 64位 虚拟机中分别为 32 bit 和 64 bit ,对象在运行的时候数据其实很多,其实已经超出了32bit和64bit,但是对象头信息是与对象自身定义的无关额外存储成本,考虑到虚拟机的空间效率,MarkWord 被设计成为一个非固定的数据结构,以便在极小的空间中存储更多的信息,他会根据自己的状态复用自己的存储空间。
25 bit 存储 hashcode,4bit 存储对象分代年龄,2bit 用于存储锁标志位,1 bit 固定为 0。
类型指针:对象指向他的类元数据(方法区中)的指针,虚拟机通过这个指针确定这个对象是哪个类的实例。
如果对象是一个 Java 数组还应该有一块存储数组长度的数据,因为普通Java对象的元数据信息确定元数据的大小,但是数组无法确定数组的大小。
1.2 实例数据(Instance Data)
实例数据是对象真正存储的有效信息, 也是在程序代码中所定义的各种类型的字段数据,包括从父类继承下来的数据。
实例数据的存储顺序会受到虚拟机的的分配策略影响,分配策略中相同宽度的数据总是放在一块,顺序如:longs/doubles、ints、shorts/chars、bytes、boolean,在满足相同宽度的数据放在一块,父类中定义的变量会出现在子类之前。
1.3 对齐填充(Padding)
HotSpot VM 的自动内存管理系统要求对象的起始地址必须是 8 字节的整数倍,换句话说对象的大小必须是8字节的倍数,如果不够8字节的倍数,就用对齐填充补全,所以说对齐填充不一定存在(对象的大小是8字节的倍数)也没有什么特别的含义。
2. 对象访问定位
Java 程序需要通过栈(虚拟机栈)上的 reference 数据来操作堆上的具体对象,由于reference 数据只是定义了一个指向对象的引用,引用通过何种方式去定位、访问堆中的对象的具体位置,取决于虚拟机,常用的两种方式:使用句柄,直接指针。
使用句柄 这种访问方式,Java 堆中会划分出一块内存作为句柄池, reference 存储的是句柄的指针,句柄包含了对象实例数据和类型数据各自的指针。如下图所示(该图选自:深入理解Java虚拟机:JVM高级特性与最佳实践 / 周志明老师著,下同)。
直接指针 访问方式,Java 堆对象中存储的直接是对象地址,Java堆中需要考虑如何放置访问类型数据的相关信息。
使用句柄 的优势,对象被移动只需修改 句柄池即可,无需修改 reference。
使用直接指针 的优势,访问速度相较于 使用句柄 更快,因为节约了一次的指针定位的开销。
在 Sun HotSpot 中 使用直接指针访问对象。
参考文献
- 深入理解Java虚拟机:JVM高级特性与最佳实践 / 周志明著. —— 2 版 . —— 北京:机械工业出版社,2013.6 (2019.1重印)