美团面试官问我: ZGC 的 Z 是什么意思

现代垃圾收集器的演进大部分都是往减少停顿方向发展。

像 CMS 就是分离出一些阶段使得应用线程可以和垃圾回收线程并发,当然还有利用回收线程的并行来减少停顿的时间。

基本上 STW 阶段都是利用多线程并行来减少停顿时间,而并发阶段不会有太多的回收线程工作,这是为了不和应用线程争抢 CPU,反正都并发了慢就慢点(不过还是得考虑内存分配速率)。

而 G1 可以认为是打开了另一个方向的大门:只回收部分垃圾来减少停顿时间

不过为了达到只回收部分 reigon,每个 region 都需要 RememberSet 来记录各 region 之间的引用。这个内存的开销其实还是挺大的,可能会占据整堆的20%或以上。

并且 G1 还有写屏障的开销,虽说用了 logging wtire barrier,但也还是有开销的。

当然 CMS 也用了写屏障,不过逻辑比较简单,啥都没判断就单纯的记录。

其实 G1 相对于 CMS 只有在大堆的场景下才有优势,CMS 比较伤的是 remark 阶段,如果堆太大需要扫描的东西太多。

而 G1 在大堆的时候可以选择部分收集,所以停顿时间有优势。

今天的主角 ZGC 和 G1 一样是基于 reigon 的,几乎所有阶段都是并发的,整堆扫描,部分收集

而且 ZGC 还不分代,就是没分新生代和老年代。

那它为啥比 G1 要牛皮?今天咱们就来盘一盘。

本文会先介绍 ZGC 的特性,或者说几个关键点,然后再简述下整体回收流程

基本上看下来对 ZCG 心中就有数了,作为普通的 Javaer,了解到这个程度就差不多了。

美团面试官问我: ZGC 的 Z 是什么意思

好了,让我们进入今天的正题!

ZGC 的目标

垃圾收集器设计出来都有目标的,有些是为了更高的吞吐,有些是为了更低的延迟。

所以我们先看看 ZGC 的目标:

美团面试官问我: ZGC 的 Z 是什么意思

可以看到它的目标就是低延迟,保证最大停顿时间在几毫秒之内,不管你堆多大或者存活的对象有多少。

可以处理 8MB-16TB 的堆。

咱们就按 openjdk 的 wiki 来展开今天的内容。

美团面试官问我: ZGC 的 Z 是什么意思

关键字:并发、基于Region、整理内存、支持NUMA、用了染色指针、用了读屏障,对了 ZGC 用的是 STAB。

Concurrent

这个 Concurrent 的意思是和应用线程并发执行,ZGC 一共分了 10 个阶段,只有 3 个很短暂的阶段是 STW 的。

美团面试官问我: ZGC 的 Z 是什么意思

可以看到只有初始标记、再标记、初始转移阶段是 STW 的。

初始标记就扫描 GC Roots 直接可达的,耗时很短,重新标记一般而言也很短,如果超过 1ms 会再次进入并发标记阶段再来一遍,所以影响不大。

初始转移阶段也是扫描 GC Roots 也很短,所以可以认为 ZGC 几乎是并发的。

而且之所以说停顿时间不会随着堆的大小和存活对象的数量增加而增加,是因为 STW 几乎只和 GC Roots 集合大小有关,和堆大小没啥关系。

这其实就是 ZGC 超过 G1 很关键的一个地方, G1 的对象转移需要 STW 所以堆大需要转移对象多,停顿的时间就长了,而 ZGC 有并发转移

不过并发回收有个情况就是回收的时候应用线程还是在产生新的对象,所以需要预留一些空间给并发时候生成的新对象。

如果对象分配过快导致内存不够,在 CMS 中是发生 Full gc,而 ZGC 则是阻塞应用线程。

所以要注意 ZGC 触发的时间。

ZGC 有自适应算法来触发也有固定时间触发,所以可以根据实际场景来修改 ZGC 触发时间,防止过晚触发而内存分配过快导致线程阻塞。

还有设置 ParallelGCThreads 和 ConcGCThreads,分别是 STW 并行时候的线程数和并发阶段的线程数来加快回收的速度。

不过 ConcGCThreads 数量需要注意,因为此阶段是和应用线程并发,如果线程数过多会影响应用线程

其实 ZGC 的每个阶段都是串行的,所以理论上其实可以不需要分两类线程,那为什么分了这两类线程?

就是为了灵活设置。分成两类就可以通过配置来调优,达到性能最大值。

对了上面提到 ZGC 的 STW 和 GC Roots 集合大小有关系,所以如果在会生成很多线程、动态加载很多 ClassLoader 等情况下会增加 ZGC 的停顿时间。

这点需要注意。

Region-based

为了能更细粒度的控制内存的分配,和 G1 一样 ZGC 也将堆划分成很多分区。

分了三种:2MB、32MB 和 X*MB(受操作系统控制)。

下图为源码中的注释:

美团面试官问我: ZGC 的 Z 是什么意思

对于回收的策略是优先收集小区,中、大区尽量不回收。

Compacting

和 G1 一样都分区了所以肯定从整体来看像是标记-复制算法,所以也是会整理的。

因此 ZGC 也不会产生内存碎片。

具体的流程下文再做分析。

NUMA-aware

以前的 G1 是不支持的,不过在 JDK14 G1 也支持了。

美团面试官问我: ZGC 的 Z 是什么意思

可能有的同学对 NUMA 不太熟悉,没事我先来解释一波。

在早期处理器都是单核的,因为根据摩尔定律,处理器的性能每隔一段时间就可以成指数型增长。

而近年来这个增长的速度逐渐变缓,于是很多厂商就推出了双核多核的计算机。

早期 CPU 通过前端总线到北桥到内存总线然后才访问到内存。

美团面试官问我: ZGC 的 Z 是什么意思

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpwyyd.html