如果说收集算法是内存回收的方法论,那垃圾收集器就是内存回收的实践者。本文主要介绍两种低延迟垃圾收集器-Shenandoah和ZGC。
从代码历史渊源上讲,比起稍后要介绍的有着Oracle正朔血统的ZGC,Shenandoah反而更像是G1的下一代继承者,它们两者有着相似的堆内存布局,在初始标记、并发标记等许多阶段的处理思路上都高度一致,甚至还直接共享了一部分实现代码,这使得部分对G1的打磨改进和Bug修改会同时反映在Shenandoah之上,而由于Shenandoah加入所带来的一些新特性,也有部分会出现在G1收集器中,譬如在并发失败后作为“逃生门”的Full GC,G1就是由于合并了Shenandoah的代码才获得多线程Full GC的支持。
1、Shenandoah支持并发的整理算法,在回收阶段能与用户线程一起并发执行。G1的回收阶段是可以多线程并行的,但却不能与用户线程并发。
2、Shenandoah处于性价比的权衡,默认不使用分代收集。
3、Shenandoah摈弃了记忆集,采用“连接矩阵(
Connection Matrix
)”的全局数据结构来记录跨Region的引用关系,降低了处理跨代指针时的记忆集维护消耗,也降低了伪共享问题的发生概率。
连接矩阵示意图如下:
Shenandoah收集器的工作过程大致可以划分为以下九个阶段。
·初始标记(Initial Marking):与G1一样,首先标记与GC Roots直接关联的对象,这个阶段仍 是“Stop The World”的,但停顿时间与堆大小无关,只与GC Roots的数量相关。
·并发标记(Concurrent Marking):与G1一样,遍历对象图,标记出全部可达的对象,这个阶段 是与用户线程一起并发的,时间长短取决于堆中存活对象的数量以及对象图的结构复杂程度。
·最终标记(Final Marking):与G1一样,处理剩余的SATB扫描,并在这个阶段统计出回收价值 最高的Region,将这些Region构成一组回收集(Collection Set)。最终标记阶段也会有一小段短暂的停 顿。
·并发清理(Concurrent Cleanup):这个阶段用于清理那些整个区域内连一个存活对象都没有找到 的Region(这类Region被称为Immediate Garbage Region)。
·并发回收(Concurrent Evacuation):并发回收阶段是Shenandoah与之前HotSpot中其他收集器的 核心差异。在这个段,Shenandoah要把回收集里面的存活对象先复制一份到其他未被使用的Region之中。复制对象这件事情如果将用户线程冻结起来再做那是相当简单的,但如果两者必须要同时并发进行的话,就变得复杂起来了。其困难点是在移动对象的同时,用户线程仍然可能不停对被移动的对象进行读写访问,移动对象是一次性的行为,但移动之后整个内存中所有指向该对象的引用都还是旧对象的地址,这是很难一瞬间全部改变过来的。对于并发回收阶段遇到的这些困难,Shenandoah将会通过读屏障和被称为“Brooks Pointers”的转发指针来解决。并发回收阶段运行的时间长短取决于回收集的大小。
·初始引用更新(Initial Update Reference):并发回收阶段复制对象结束后,还需要把堆中所有指 向旧对象的引用修正到复制后的新地址,这个操作称为引用更新。引用更新的初始化阶段实际上并未做什么具体的处理,设立这个阶段只是为了建立一个线程集合点,确保所有并发回收阶段中进行的收集器线程都已完成分配给它们的对象移动任务而已。初始引用更新时间很短,会产生一个非常短暂的 停顿。
·并发引用更新(Concurrent Update Reference):真正开始进行引用更新操作,这个阶段是与用户 线程一起并发的,时间长短取决于内存中涉及的引用数量的多少。并发引用更新与并发标记不同,它不再需要沿着对象图来搜索,只需要按照内存物理地址的顺序,线性地搜索出引用类型,把旧值改为 新值即可。
·最终引用更新(Final Update Reference):解决了堆中的引用更新后,还要修正存在于GC Roots中的引用。这个阶段是Shenandoah的最后一次停顿,停顿时间只与GC Roots的数量相关。
·并发清理(Concurrent Cleanup):经过并发回收和引用更新之后,整个回收集中所有的Region已再无存活对象,这些Region都变成Immediate Garbage Regions了,最后再调用一次并发清理过程来回收这些Region的内存空间,供以后新对象分配使用。
这九个阶段的工作过程可能拆的比较琐碎,只要抓住其中三个最重要的并发节点(并发标记、并发回收、并发引用更新)就好理解Shenandoah的运作过程了。
“Brooks”是一个人的名字。1984年,Rodney A.Brooks在论文《Trading Data Space for Reduced Time and Code Space in Real-Time Garbage Collection on Stock Hardware》中提出了使用转发 指针(Forwarding Pointer,也常被称为Indirection Pointer)来实现对象移动与用户程序并发的一种解决方案。
Brooks Pointers示意图:
Brooks提出的新方案不需要用到内存保护陷阱,而是在原有对象布局结构的最前面统一增加一个新的引用字段,在正常不处于并发移动的情况下,该引用指向对象自己。
转发指针加入后带来的收益自然是当对象拥有了一份新的副本时,只需要修 改一处指针的值,即旧对象上转发指针的引用位置,使其指向新对象,便可将所有对该对象的访问转 发到新的副本上。这样只要旧对象的内存仍然存在,未被清理掉,虚拟机内存中所有通过旧引用地址 访问的代码便仍然可用,都会被自动转发到新对象上继续工作,如下图所示:
ZGC和Shenandoah的目标是高度相似的,都希望在尽可能对吞吐量影响不太大的前提下,实现在任意堆内存大小下都可以把垃圾收集的停顿时间限制在十毫秒以内的低延迟。
ZGC采用基于Region的堆内存布局,ZGC的Region(在一些官方资料中将它称为Page或者ZPage)具有动态性——动态创建和销毁,以及动态的区域容量大小。在x64硬件平台下,ZGC的 Region可以具有大、中、小三类容量:
小型Region(Small Region):容量固定为2MB,用于放置小于256KB的小对象。
中型Region(Medium Region):容量固定为32MB,用于放置大于等于256KB但小于4MB的对 象。·
大型Region(Large Region):容量不固定,可以动态变化,但必须为2MB的整数倍,用于放置 4MB或以上的大对象。每个大型Region中只会存放一个大对象,这也预示着虽然名字叫作“大型Region”,但它的实际容量完全有可能小于中型Region,最小容量可低至4MB。大型Region在ZGC的实 现中是不会被重分配。
示意图如下:
染色指针是一种将少量额外信息存储在指针上的技术。在Linux下64位指针的高18位不能用来寻址,在剩下的的46位中,将其高4位提取出来存储4个标志信息。
染色指针示意图如下:
三大优势:
染色指针可以使得某个Region的存活对象被移走后,这个Region立即就能够被释放和重用掉。
染色指针可以大幅减少在垃圾收集过程中内存屏障的使用数量,这样对程序运行效率是大有裨益的,所以ZGC对吞吐量的影响相对较低。
染色指针可以作为一种可扩展的存储结构用来记录更多与对象标记、重定位过程相关的数据,以便日后进一步提高性能。
在x86-64平台上,ZGC设计者采用虚拟内存映射技术,将多个不同的虚拟内存地址映射到同一个物理内存地址上,这是一种多对一映射,多重映射是染色指针技术的伴生产物。
多重映射下的寻址示意图如下:
可分以下四个阶段:
并发标记(Concurrent Mark):遍历对象图做可达性分析,在指针上更新Marked0、Marked1标志位。
并发预备重分配(Concurrent Prepare for Relocate):根据特定的查询条件统计得出本次收集过程要清理哪些Region,
将这些Region组成重分配集。每次回收都会扫描所有的Region。并发重分配(Concurrent Relocate):把重分配集中的存活对象复制到新的Region上,并为重分配集中的每个Region维护一个转发表,记录从就对象到新对象的转向关系。
并发重映射(Concurrent Remap):修正整个堆中指向重分配集中的旧对象的所有引用,这不是必须且迫切要完成的任务,会合并到下一次垃圾收集循环中的并发标记阶段。一旦所有指针都被修正之后,原来记录新旧对象关系的转发表就可以释放掉了。
支持“NUMA-Aware”的内存分配,是一种为多处理器或者多核处理器的计算机所设计的内存架构。内存控制器被集成到了处理器的内核中,每个处理核心都有属于自己内存管理器所管理的内存,访问其他处理器核心管理的内存必须通过Inter-Connect通道来完成,ZGC收集器会优先尝试请求线程当前所处的处理器的本地内存上分配对象,以保证高效内存访问。
Shenandoah收集器通过比较并交换操作来保证并发时对象的访问正确性。
ZGC的设计理念与Azul System公司的PGC和C4收集器一脉相承,是迄今垃圾收集器研究的最前沿成果,它与Shenandoah一样做到了几乎整个收集过程都全程可并发,短暂停顿也只与GC Roots大小相关而与堆内存大小无关,因而同样实现了任何堆上停顿都小于十毫秒的目标。
点赞 收藏 关注
晚风中闪过,有几帧从前啊。