Back
Featured image of post JVM 学习笔记 03 - Hotspot 对象内存

JVM 学习笔记 03 - Hotspot 对象内存

JVM 学习笔记,本文详细讲解了在 Hotspot VM 上对象创建和访问的全过程,以及它的内存布局。

导航页

对象创建

书接上回,我们接着来讲解对象创建。

JVM 的内存分配的重中之重就是 Heap 上对象的分配。它是最大,同时也最常访问的一块内存。

以下内容即为 Hotspot VM 在创建对象时所做的工作:

  1. 在新建对象时,JVM 会首先检查常量池中是否有对应类的符号引用,若无,则执行响应的类加载过程。

  2. 之后 JVM 将会为新生对象分配内存,而对象所需内存在类加载完毕后是完全确定的。所以实质上就是从 Heap 取一块固定大小的内存。

    • 取 Heap 有多种不同实现,若假设内存绝对规整,即使用过的内存和未使用内存界限分明,中间使用一个指针作为分界点指示器。若要分配内存,则只需要挪动对应的指针距离,这种方式叫「指针碰撞」(Bump The Pointer)。
    • 然而,维护规整的内存价格是高昂的。因此,很多时候使用和未使用内存是交错的,也就无法指针碰撞。那么 JVM 就必须维护一个列表,从列表中查找可用的内存并分配。这种方式名为「空闲列表」(Free List)。
    • 选择哪种方式是由 JVM 实现决定的。有些 GC ,如 Serial、ParNew,使用 Compact 方式垃圾收集,因此对应的分配算法即为指针碰撞;而诸如 CMS 这种,使用 Sweep 算法,则需要使用 free list。
    • 另外,对象创建需要考虑线程安全。有的虚拟机使用 CAS 原子操作 + 失败重试机制;而有的虚拟机还使用了 TLAB(Thread Local Allocation Buffer,本地线程分配缓存),先在缓存中分配,用尽时再同步锁定。可以通过 -XX:+/-UseTLAB 参数尝试设定。
  3. 内存分配完毕后,虚拟机会将对象初始化为零值。以避免访问到意外的数值。在 native 语言中常常需要开发者手动初始化。

  4. 之后,JVM 会设置基本的对象信息,例如对象的类型信息、元数据、哈希码、GC 分代等存放在对象头(Object Header)中的数据。还涉及对锁的优化。

  5. Done,虚拟机需要做的工作已经完成了。之后会执行对应的 <init>() 方法。

对象布局

Hotspot VM 中,对象的 Heap 存储布局可以分为三部分:对象头、实例数据、对齐填充(Header、Instance Data、Padding)。


对象头可以再细分两类:

  1. Mark Word 储存对象自身的运行时数据,如 HashCode、GC 分代年龄、锁状态、持有的锁等,若未开启压缩指针,大小则对应操作系统位数 32 或 64 位。Mark Word 被设计为动态的数据结构,以尽量存储更多的数据。
  2. 另一部分就是类型指针了。就是指向对象类型元数据的指针,JVM 通过此指针确定该对象是哪个类的实例。当然也不是所有虚拟机查找元数据都需要经过对象本身。另外对于数据,还需要存储数组长度。

接下来的实例数据部分,是对象的有效定义信息,即各种在代码中定义的字段内容,包括父类和自身的定义。

在 C 中,结构体字段存放的顺序,会影响运行时的对齐行为。而 JVM 中是由具体虚拟机实现和定义顺序共同决定的。可以通过 -XX:FieldsAllocationStyle 参数指定策略。

Hotspot VM 默认的分配顺序为 long、doube、int、short、char、oops(Ordinary Object Pointers, OOPs)。可以看出策略是让等宽的数据靠在一起。此外,+XX:CompactFields 为 true 时(默认为 true)会允许父类和子类混合顺序,否则就会按照父类在前子类在后的顺序分配。


为了使得 CPU 寻址时获得最高效率。HotSpot VM 要求对象起始地址为 8 byte 的倍数。因此非此倍数的对象会添加 padding。

对象访问

对象创建就是为了访问的。JVM 规范定义,Stack 上具有 Reference 数据来操作堆上的实体对象。当然了,这个 Reference 如何实现,也是看虚拟机怎么做。常用的有两种实现:

  1. 使用句柄(Handle)访问。Heap 中会划出一段句柄池,ref 的就是对象的句柄地址,其中存有对象实例和对象类型的数据地址。
JVM 通过句柄访问对象示意图
  1. 直接通过指针(Pointer)访问。此时 Heap 的内存布局则需要考虑如何放置类型数据地址。但也减少了一次间接访问的开销。
JVM 通过指针访问对象示意图

两种方式各有优势,使用句柄可以稳定局部变量。在对象移动时(GC 时常常移动对象)会只需要改变句柄中的指针,而无需修改 ref 本身。

而直接指针最大的好处就是速度快。节省了一次定位开销。更何况对象访问是十分频繁的行为。HotSpot VM 就是用的直接指针访问方式。

本节内容就到这里,下一篇文章将会讲解 GC 相关的内容。

comments powered by Disqus