ZGC的7种垃圾回收触发时机 | 掘金征文

3,066 阅读9分钟

ZGC作为新一代垃圾回收器,想必大家已经不陌生了。虽然目前在国内使用ZGC作为生产环境垃圾回收器的公司并不多,但是我们不能否认,ZGC在未来会有很大的前景。那么作为一名技术者,我们需要时刻对新技术保持关注。小编最近正在潜心学习ZGC这款新型垃圾回收器,闲暇之余,写下了这篇文章,分享给大家。

ZGC中,为了实现更高的性能,尽量避免进行同步垃圾回收,也就是说尽量避免触发同步的垃圾回收的消息。ZGC中触发同步消息的场景也比较少,总体以触发异步消息为主。异步消息主要由ZDirector根据规则判断是否可以触发,在ZDirector流程中有4种触发规则,本文主要介绍这4种规则是如何触发的,最后,还会简要介绍其他的垃圾回收消息是如何触发的。

一、基于固定时间间隔触发

ZDirector提供的第一个规则就是基于固定时间间隔触发垃圾回收。这个规则的目的非常简单,就是希望ZGC的垃圾回收器以固定的频率触发。在这一些场景中非常有用,例如我们的应用程序在晚上请求量比较低的情况下运行了很长时间,但是ZGC不满足其他垃圾回收器的触发条件,所以一直不会触发垃圾回收,这通常没什么问题,如果在早上某一个时间点开始请求量暴增,这可能导致内存使用也暴增,而垃圾回收器来不及回收垃圾对象,将降低应用系统的吞吐量。所以ZGC提供了基于固定时间间隔触发垃圾回收的规则。
    这个规则的实现也非常简单,就是判断前一次垃圾回收结束到当前时间是否超过时间间隔的阈值,如果超过,则触发垃圾回收,如果不满足,则直接返回。
    需要说明的是,时间间隔由一个参数ZCollectionInterval来控制,这个参数的默认值为0,表示不需要触发垃圾回收。实际工作中,可以根据场景设置该参数。

二、预热规则触发

ZDirector提供的第二个规则是预热启动垃圾回收。为什么设计这一规则?设计这一规则的目的是当JVM刚启动时,还没有足够的数据来主动触发垃圾回收的启动,所以设置了预热规则。
    预热规则指的是JVM启动后,当发现堆空间使用率达到10%、20%和30%时,会主动地触发垃圾回收。ZGC设计前3次垃圾回收可由预热规则触发,也就是说当垃圾回收触发(无论是由预热规则,还是主动触发垃圾回收)的次数超过3次时,预热规则将不再生效。

三、根据分配速率

ZDirector提供的第三个规则是根据分配速率来预测是否能触发垃圾回收。这一规则设计的思路是:
    1)收集数据:在程序运行时,收集过去一段时间内垃圾回收发生的次数和执行的时间、内存分配的速率MEMratio和当前空闲内存的大小MEMfree。
    2)计算:根据过去垃圾回收发生的情况预测下一次垃圾回收发生的时间TIMEgc,按照内存分配的速率预测空闲内存能支撑应用程序运行的实际时间TIMEoom,例如TIMEoom=MEMfree/MEMratio。
    3)设计规则:如当TIMEoom小于TIMEgc(垃圾回收的时间),则可以启动垃圾回收。这个规则的含义是如果从现在起到OOM发生前开始执行垃圾回收,刚好在OOM发生前完成垃圾回收的动作,从而避免OOM。在ZGC中ZDirector是周期运行的,所以在计算时还应该把OOM的时间减去采样周期的时间,采样周期记为TIMEinterval,则规则为TIMEoom<TIMEgc+TIMEinterval时触发垃圾回收。
    那么最主要的任务就变成了如何预测下一次垃圾回收时间TIMEgc和内存分配速率MEMratio(因为MEMfree是已知数据,无需额外处理)。
    我们以预测垃圾回收时间TIMEgc为例来看看如何预测。最简单也最直观的思路是,根据已经发生的垃圾回收所使用的时间来预测下一次垃圾回收可能花费的时间,那么就有了如下思路:
    1)收集过去一段时间内垃圾回收发生的次数和时间,取过去N次垃圾回收的平均时间作为下一次垃圾回收的预测时间;这一方法最为直观,但是准确度可能有待提高。
    2)收集过去一段时间内垃圾回收发生的次数和时间,建立一个逻辑回归模型,从而预测下一次垃圾回收的预测时间;这一方法虽然比第一种方法有改进,根据垃圾回收的趋势来预测下一次垃圾回收的时间,但这一方法最大的问题是逻辑回归模型太简单,实际上如果我们能提供更多的输入,比如应用程序使用内存的情况、线程数等建立动态模型,这应该是一个非常好的方法。
    3)使用衰减平均时间来预测下一次垃圾回收花费的时间。衰减平均方法实际上是第一种方法和第二种方法组合后的一种简化实现。它是一种简单的数学方法,用来计算一组数据的平均值,但是在计算平均值的时候最新的数据有更高的权重,即强调近期数据对结果的影响。在G1中预测下一次垃圾回收时间采用的就是这种方法。
    4)直接采用已经成熟的模型来预测下一次垃圾回收时间。ZGC中主要是基于正态分布来预测。
    学过概率论的同学大多知道这一概念。我们先来回顾一下正态分布。首先它是一条中间高,两端逐渐下降且完全对称的钟形曲线。正态分布也非常容易理解,指的是大多数数据应该集中在中间附近,少数异常的情况才会落在两端。
    对于垃圾回收算法中的数据:内存的消耗时间,垃圾回收的时间也应该符合这样的分布。注意,并不是说G1中的停顿预测模型不正确或者效果不好,而是说使用正态分布来做预测有更强的数学理论支撑。

四、主动触发

ZDirector提供的第四个规则是主动触发规则,该规则是为了应用程序在吞吐量下降的情况下,当满足一定条件时,还可以执行垃圾回收。这里满足一定条件指的是:
    1)从上一次垃圾回收完成到当前时间,应用程序新增使用的内存达到堆空间的10%。
    2)从上一次垃圾回收完成到当前时间已经过去了5min,记为TIMEelapsed。
    如果这两个条件同时满足,预测垃圾回收时间为TIMEgc,定义规则:如果NUMgc * TIMEgc < TIMEelapsed,则触发垃圾回收。其中NUMgc是ZGC设计的常量,假设应用程序的吞吐率从50%下降到1%,需要触发一次垃圾回收。
    这个规则实际上是为了弥补程序吞吐率骤降且长时间不执行垃圾回收而引入的。有一个诊断参数ZProactive来控制是否开启和关闭主动规则,默认值是true,即默认打开主动触发规则。
    实际上这个规则和第一个规则(基于固定时间间隔规则)在某些场景中有一定的重复,第一个规则只强调时间间隔,本规则除了考虑时间之外还会考虑内存的增长和吞吐率下降的快慢程度。

五、阻塞内存分配请求触发

阻塞内存分配由参数ZStallOnOutOfMemory控制,当参数ZStallOnOutOfMemory为true时进行阻塞分配,如果不能成功分配内存,则触发阻塞内存分配。     注意:该触发请求是异步的,并非同步消息。页面阻塞分配会触发垃圾回收,直到垃圾回收完成并成功分配页面为止。因为是异步消息,所以页面阻塞分配请求需要额外的实现等待成功分配的功能,其实非常简单,可以通过一个循环来实现。
    那为什么ZGC不把阻塞内存分配实现成同步消息,而是通过异步消息加上循环的方式?
    原因在于同步消息请求的线程在发出同步消息后是通过通知等待机制完成的,通知等待机制通常会让出CPU,而页面阻塞分配采用异步消息加上循环的方式,这样的设计可以减少页面分配时因线程调度带来的额外开销。从这一点也可以看出,设计一款优秀的软件,需要从每一个细节出发,并仔细斟酌。

六、外部触发

外部触发是指在Java代码中显式地调用System.gc()函数,在JVM执行该函数时,会触发垃圾回收。该触发请求是从用户代码主动触发的,从编程角度来看,说明程序员认为此时需要进行垃圾回收(当然首先是程序员正确使用System.gc()函数),所以ZGC把该触发规则设计为同步请求,只有在执行完垃圾回收后,才能进行后续代码的执行。

七、元数据分配触发

元数据分配失败时,ZGC会尝试进行垃圾回收以确保元数据能正确分配。     异步垃圾回收后会尝试是否可以分配元数据对象空间,如果不能,将尝试进行同步垃圾回收后可以分配元数据对象空间,如果还不成功,则尝试扩展元数据空间,再分配成功则返回内存空间,不成功则返回NULL。

掘金征文 | 2020 与我的年中总结 征文活动正在进行中......