为 IBM® SDK, JavaTM Technology Edition 提供技术支持的 Eclipse OpenJ9 Virtual Machine (J9VM),的默认垃圾收集 (GC) 策略是分代并发策略 (gencon)。gencon 策略将 Java 堆划分为两个区,分别称为婴儿区 (nursery) 和长存区 (tenured)。婴儿区进一步划分为两个空间,分别称为分配空间和存留空间。对象被分配在婴儿区的分配空间中。当此空间填满时,会通过一个称为清扫 (scavenge) 的过程中将活动对象疏散到存留空间。然后交换分配空间与存留空间的角色。如果对象在经历一定次数的婴儿区收集后存留下来,就会升级到长存区。对于大部分 Java 应用程序,大部分对象都是短期的。这意味着婴儿区收集可以高效地回收短期对象的内存,而不需要收集整个堆。升级到长存区的长期对象的收集频率更低,为在 GC 上花的时间实现了更高的内存回收投资回报。

并发清扫 (Concurrent Scavenge) 是 J9VM 中的 GC 的一个特性,具体来讲它遵守 gencon GC 策略,旨在在 Java 应用程序线程运行期间,通过执行清扫阶段来最小化暂停整个应用程序的时间。在清扫周期开始时,短暂地挂起应用程序线程以复制静态对象、堆栈槽和寄存器中引用的活动对象。然后恢复应用程序线程和 GC 线程,后者可以开始将活动对象从分配空间清扫到存留空间。应用程序线程加载的任何对象引用都必须经历一个读屏障 (Read Barrier),该屏障确定加载的值是否在分配空间内。对分配空间的引用是:

  • 对一个必须重新放到存留空间的活动对象的引用
  • 或者已被 GC 清扫的活动对象的原始位置的过时引用

在这种情况下,如果有必要,引用的对象然后会迁移到存留空间,并更新引用。该过程持续到所有活动对象都清扫到存留空间。在清扫周期结束时,再次短暂挂起应用程序线程,以执行清理、统计,以及交换分配空间与存留空间。

图 1:可通过两种方法允许 GC 重新放置对象并更新引用,同时确保应用程序线程看到任何对象的准确引用。

由于在加载每个对象引用之前都需要检查该对象是否已在分配空间中,所以读屏障的任何软件实现都非常慢。所以,J9VM 使用读屏障的硬件实现。受保护存储 (Guarded Storage, GS) 工具是 IBM z14TM (z14) 大型机的一个特性,允许程序保护内存中的区域,以便对该区域的引用执行保护性加载时会触发一个用户定义的中断处理程序。婴儿区作为受保护的存储区而映射和注册到该工具。硬件将此区域划分为 64 个区段,并提供一种屏蔽能力来选择性地启用这 64 个区段的一个要保护的子集。在清扫周期开始时,会选择性地启用与婴儿区的分配空间对应的掩码。在该周期中,应用程序线程会使用一组新的受保护加载指令来加载任何对象引用。当硬件执行受保护加载指令时,如果启用了受保护存储工具,它会确定加载的值是否包含在任何活动的受保护区段中,GC 已确保这些区段与分配空间对应。如果该值包含在一个受保护区域中,GS 工具会触发一个交给中断处理程序处理的陷阱,中断处理程序在必要时将引用的对象迁移到存留空间并更新该引用。在清扫周期结束时,会在所有进程上禁用受保护存储工具。

在developerWorks上的相关资源:


本文翻译自:How Concurrent Scavenge using the Guarded Storage Facility Works(2017-10-05)

加入讨论