衡量垃圾收集器的三项最重要的指标:内存占用、吞吐量、延迟。
目前处于实验状态的垃圾收集器:Shenandoah、ZGC可以在任意可管理的对容量下,实现停顿不超过十毫秒这种天方夜谭的话。
1. shenandoah收集器
背景故事:RedHat开发的,被排挤。
目标:实现一种能在任何堆内存大小下都可以吧垃圾收集的停顿时间限制在十毫秒以内的垃圾收集器。
结构:Shenandoah的结构和G1的结构基本相同。但有三个不同之处:
-
支持与用户线程一起并发的整理算法。
-
默认不使用分代。
-
用“链接矩阵”的全局数据结构替代记忆表来记录跨Region的引用关系,降低伪共享的发生概率。
工作过程:
-
初始标记:与G1一样,首先标记与GC Roots直接关联的对象, 这个阶段仍是“Stop The World"的,但停顿时间与堆大小无关,只与GC Roots的数量相关。
-
并发标记(Concurrent Marking) :与G1一样,遍历对象图,标记出全部可达的对象,这个阶段是与用户线程一起并发的,时间长短取决于堆中存活对象的数量以及对象图的结构复杂程度。
-
最终标记(Final Marking) :与G1一样,处理剩余的SATB扫描,并在这个阶段统计出回收价值最高的Region,将这些Region构成一组回收集(Collection Set)。最终标记阶段也会有一小段短暂的停顿。
-
并发回收:并发回收阶段是Shenandoah与之前HotSpot中其他收集器的核心差异。在这个阶段,Shenandoah要把回收集里面的存活对象先复制一份到其他未被使用的Region之中。复制对象这件事情如果将用户线程冻结起来再做那是相当简单的,但如果两者必须要同时并发进行的话,就变得复杂起来了。其困难点是在移动对象的同时,用户线程仍然可能不停对被移动的对象进行读写访问,移动对象是一次性的行为,但移动之后整个内存中所有指向该对象的引用都还是旧对象的地址,这是很难一瞬间全部改变过来的。对于并发回收阶段遇到的这些困难,Shenandoah将会通过读屏障和被称为“Brooks Pointers"的转发指针来解决(讲解完Shenandoah整个工作过程之后笔者还要再回头介绍它)。并发回收阶段运行的时间长短取决于回收集的大小。
-
初始引用更新:并发回收阶段复制对象后,还需要把堆中所有指向就对象的引用修正到复制后的新地址,这个操作称为引用更新。
-
并发引用更新:真正开始进行引用更新操作,这个阶段是与用户线程一起并发的,时间长短取决于内存中涉及的引用数量的多少。
-
最终引用更新:解决了堆中的引用更新后,还要修正存在于GC Roots中的引用。
-
并发清理:经过并发回收和引用更新之后,整个回收集中所有的Region已再无存活对象,这些Region都变成Immediate Garbage Regions了,最后再调用一-次并发清理过程来回收这些Region的内存空间,供以后新对象分配使用。
我们只需要抓住以上三个重要的并发阶段:并发标记、并发回收、并发应用更新,就能理清Shenandoah是如何运作的了。
2. ZGC收集器
ZGC收集器是一款基于Region内存布局的,不设分代的,使用了读屏障、染色指针和内存多重映射等技术来实现可并发的标记-整理算法的,已低延迟为首要目标的一款垃圾收集器
特点:
Region:ZGC的Region具有动态性——动态创建和销毁,以及动态的区域容量大小。在X64硬件平台下,ZGC的Region具有大、中、小三类容量:
-
小型Region ( Small Region) :容量固定为2MB,用于放置小于256KB的小对象。
-
中型Region ( M edium Region) :容量固定为32MB,用于放置大于等于256KB但小于4M B的对象。
-
大型Region (Large Region) :容量不固定,可以动态变化,但必须为2MB的整数倍,用于放置4MB或以上的大对象。每个大型Region中只会存放一个大对象,这也预示着虽然名字叫作“大型Region”,但它的实际容量完全有可能小于中型Region,最小容量可低至4MB。大型Region在ZGC的实现中是不会被重分配(重分配是ZGC的一-种处理动作,用于复制对象的收集器阶段,稍后会介绍到)的,因为复制一个大对象的代价非常高昂。
并发整理算法的实现:ZGC用到了读屏障和染色指针技术。染色指针是一种直接将少量额外的信息存储在指针上的技术。
染色指针的三大优势:
-
染色指针可以使得一旦某个Region的存活对象被移走之后,这个Region立即就能够被释放和重用掉,而不必等待整个堆中所有指向该Region的引用都被修正后才能清理。
-
染色指针可以大幅减少在垃圾收集过程中内存屏障的使用数量,设置内存屏障,尤其是写屏障的目的通常是为了记录对象引用的变动情况,如果将这些信息直接维护在指针中,显然就可以省去一些专门的记录操作。
-
染色指针可以作为一种可扩展的存储结构用来记录更多与对象标记、重定位过程相关的数据,以便日后进一步提高性能。