深入理解JVM(4)——对象的创建和访问

时间:2023-03-10 07:00:52
深入理解JVM(4)——对象的创建和访问

1、对象的创建

在语言层面上,创建对象(例如克隆,反序列化)通常仅仅是一个new关键字而已。

在虚拟机中,对象(文中讨论的对象限于普通 Java 对象,不包括数组和 Class 对象等)的创建过程如下:

深入理解JVM(4)——对象的创建和访问

1.1、分配内存

空间分配的两种方式

  • 指针碰撞:当已分配空间被集中存放,已分配和未分配空间使用一个指针来标记时,分配新的空间只需要移动该指针即可,此方法为指针碰撞。适用于 GC 算法会做 Compact 的情况。
  • 空闲列表:当已分配的空间是分散存放时,虚拟机必须维护一个记录了哪些内存块是可用的列表,此为空闲列表,需要分配新空间时只需要从该列表中获取。

分配方式的选择取决于由Java堆是否规整,而Java堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定。 因此,在使用 Serial、 ParNew 等带 Compact 过程的收集器时,系统采用的分配算法是指针碰撞,而使用 CMS 这种基于 Mark-Sweep 算法的收集器时,通常采用空闲列表。

解决空间分配线程安全问题的两种方式

  • CAS方式失败重试:顾名思义,遇上分配时的线程冲突时,会再次进行空间分配直至成功。实现简单直观但是效率较低
  • 空间划分:为每个线程分配单独的一块空间,该空间只用来给该线程做创建对象分配空间时使用,这单独的空间被称为本地线程分配缓冲(Thread Local Allocation Buffer,TLAB)。当该线程的 TLAB 分配光了后,才需要同步锁定,效率较高。是否使用TLAB可以通过虚拟机参数 -XX:+/-UseTLAB 指定。

2、对象的内存布局

2.1、设置对象头

对象头包含两部分信息:

  • 运行时数据(hash码、GC分代年龄、锁状态标志、 线程持有的锁、 偏向线程ID、 偏向时间戳等),这部分数据被称为 Mark Word。
  • 类型指针,用来说明该对象是哪个类的实例。

2.2、设置对象实例数据

对象的实例数据主要是根据 JAVA 代码的编写生成的,包括父类在内的各种类型的字段,其存储顺序受虚拟机分配策略参数及代码中字段定义顺序的影响。

虚拟机安排字段的方式:

  1. 相同宽度的字段放在一起
  2. 父类的字段放在子类的前面
  3. 窄小的变量也会被安排在父类的字段空隙中(C++的内存安排规则,HotSpot VM是由C++语言编写)

2.3、填充字段

HotSpot VM要求对象的起始位置必须是8字节的整数倍,也就是说对象必须是8字节的整数倍,所以需要填充占位(这也是一句C++的规则来的)。

3、对象的访问

JAVA 程序访问对象需要通过栈上的 reference 数据操作堆上的具体对象。reference 对对象的访问方式主要为句柄和直接指针两种。

句柄访问

Java堆中将会划分出一块内存来作为句柄池,reference中存储的是对象的句柄地址,句柄中包含了对象实例数据与类型数据各自的具体地址信息。

深入理解JVM(4)——对象的创建和访问

直接指针访问

reference中存储的直接就是对象地址

深入理解JVM(4)——对象的创建和访问

句柄访问方式的好处是 reference 中存储的是稳定的句柄地址,在对象被移动(垃圾收集时移动对象是非常普遍的行为)时只会改变句柄中的实例数据指针,而 reference 本身不需要修改。

直接指针访问方式的好处是速度更快,它节省了一次指针定位的时间开销。

虚拟机Sun HotSpot 使用的是直接指针访问。