HotSpot 虚拟机对象探秘

对象的内存模型

在 HotSpot 虚拟机中,对象在内存中存储的布局分为 3 块区域:

  • 对象头
  • 实例数据
  • 对齐补充

对象头

对象头记录了对象在运行过程中所需要使用的一些数据:

  • 哈希码
  • GC 分代年龄
  • 锁状态标志
  • 线程持有的锁
  • 偏向线程 ID
  • 偏向时间戳

对象头可能包含类型指针,通过该指针能确定对象属于哪个类。如果对象是一个数组,那么对象头还包括数组长度。

实例数据

实例数据部分就是成员变量的值,其中包括父类成员变量和本类成员变量。

对齐补充

用于确保对象的总长度为 8 字节的整数倍。

HotSpot 要求对象的总长度为 8 字节的整数倍。由于对象头一定是 8 字节的整数倍,但实例数据部分的长度是任意的,因此需要对齐补充字段确保整个对象的总长度为 8 的整数倍。

对齐补充并不是必然存在,也没有特别的含义,它仅仅起着占位符的作用。

对象的创建过程

类加载检查

虚拟机遇到一条 new 指令时,首先检查常量池中是否有这个类的符号引用,并且检查这个符号引用所代表的类是否已被加载、解析和初始化过。如果没有,那么必须先执行相应的类加载过程。

为新生对象分配内存

对象所需内存的大小在类加载完成后便可完全确定,接下来从堆中划分一块对应大小的内存空间给新的对象。分配堆中内存有两种方式:

  • 指针碰撞

如果 JVM 的垃圾收集器采用复制算法标记-整理法,那么堆中空闲内存是完整的区域,并且空间内存和已使用内存之间由一个指针标记。为对象分配内存时,只需移动指针即可。这种在完整空闲区域上通过移动指针来分配内存的方式称为“指针碰撞”。

  • 空闲列表

如果 JVM 的垃圾收集器采用标记-清除算法,那么堆中空闲区域和已使用区域交错,因此需要用一张“空闲列表”来记录堆中哪些区域是空闲区域,从而在创建对象的时候根据这张“空闲列表”找到空闲区域,并分配内存。

初始化

分配完内存后,为对象中的成员变量赋上初始值,设置对象头信息,调用对象的构造函数方法进行初始化。

至此,整个对象的创建过程就完成了。

对象的访问方式

所有对象的存储空间都是在堆中分配的,但是这个对象的引用却是在堆栈中分配的。也就是说在建立一个对象时两个地方都分配内存,在堆中分配的内存实际建立这个对象,而在堆栈中分配的内存只是一个指向这个堆对象的指针(引用)而已。
那么根据引用存放的地址类型的不同,对象有不同的访问方式。

句柄访问方式

堆中需要有一块叫做“句柄池”的内存空间,句柄中包含了对象实例数据与类型数据各自的具体地址信息。

引用类型的变量存放的是该对象的句柄地址(reference)。访问对象时,首先需要通过引用类型的变量找到该对象的句柄,然后根据句柄中对象的地址找到对象。

句柄访问方式

直接指针访问方式

引用类型的变量直接存放对象的地址,从而不需要句柄池,通过引用能够直接访问对象。但对象所在的内存空间需要额外的策略存储对象所属的类信息的地址。

直接指针访问方式

HotSpot 采用直接指针方式访问对象,因为它只需要一次寻址操作,从而性能比句柄访问方式快一倍。但它需要额外的策略存储对象在方法区中类信息的地址。


yanglbme
202 声望16 粉丝

GitHub: Https://github.com/yanglbme