Java 虚拟机视角下对象创建与定位的过程

一、对象的创建过程

图1. 对象创建的内存分配

1. 定位类的符号引号且初始化

当 JVM 碰到 new 执行时,会检查参数是否能定位到一个类的符号引用。若未定位到,或未初始化,需要进行类的加载流程。若定位到,且已被加载、解析和初始化过,则会进行对象内存分配(从堆中划分出来,大小在加载完成后确定)。

2. 内存分配

此时需要考虑一个问题,堆内存是否规整连续。故有两种策略进行内存分配:

  • 在内存绝对规整的情况下(即堆中内存连续的内存大小大于对象所需大小),即可直接通过向空闲空间方向移动指针完成内存分配,称为 指针碰撞
  • 在连续空间不足以分配对象所需大小空间时,虚拟机则需要维护一个 空闲列表,以记录内存块是否可用(是否未分配)。分配完成后即更新空闲列表。

使用何种分配方式由 Java 堆内存是否规整决定,而是否规整则由所采用的垃圾回收器是否带有 空间压缩能力 决定:

  • Serial、ParNew 等带压缩整理算法的收集器,使用的是指针碰撞,故此过程简单高效。
  • CMS 等基于清除算法的收集器,使用的是空闲列表来分配内存。

并发情况下内存分配:
内存分配并不是线程安全的,即使仅仅是指针移动依然产生并发问题。解决此问题有两种方案:
1. 分配过程同步处理。采用是 CAS + 失败重试的方案。
2. 使用本地线程分配缓冲(TLAB)。即每个线程在 Java 堆中预先分配一小块内存进行内存分配,使用完后再进行同步锁定进行内存分配操作。(通过 -XX:+/-UseTLAB 参数来设定是否使用)

3.分配空间初始化零值

内存分配完成后,虚拟机需要将分配到的内存空间(除对象头)初始化为零值,即可保证 Java 代码不赋初始值即可使用。若使用 TLAB,还可以提前到 TLAB 分配内存时进行初始化。

4. 对象进行必要的设置

主要为对象头中信息的设置,包括:对象是哪个类的实例、如何找到类的元数据信息、对象的 GC 分代年龄和是否启用偏向锁等。

5. 执行构造函数

从 Java 程序的视角看,上述过程仅为对象创建才刚开始。构造函数(Class 文件中的 () 方法未执行),默认都为零值。一般来说,通过 new 关键字生成的 new 指令后面会紧接着执行 () 方法(其他方式不一定如此,new 关键字会生成 invokespecial 指令,指引执行 ()),按照开发者的意图进行对象初始化,至此完成创建对象的工作。

二、对象访问定位

JVM 通过 栈上的 reference 数据来操作堆上的具体对象。对象访问方式有两种:

  • 使用句柄访问。如图2,Java 堆中将可能划分出一块区域做句柄池,reference 存储的是对象的句柄地址,句柄包含了对象实例数据与类型数据各自具体的地址信息。reference 中存储稳定句柄地址,对象被移动时(垃圾收集)只会改变实例数据指针,reference 自身保持不变。
    图2. 句柄访问对象
  • 使用直接指针访问。如图3,reference 直接存储对象地址,从而速度更快,不用进行二次寻址,由于 Java 对象访问频繁,此方式可带来较大的性能提升。而对象类型数据地址信息存储在对象头中类型指针区域,此区域指向方法区中对应存储的对象类型数据。HotSpot 采用的访问模式为此方式
    图3. 直接指针访问对象.png

你可能感兴趣的:(Java 虚拟机视角下对象创建与定位的过程)