G1是一个响应时间优先的GC算法,用户可以设定整个GC过程的期望停顿时间,由参数MaxGCPauseMillis控制,默认值200ms。不过它不是硬性条件,只是期望值,G1会努力在这个目标停顿时间内完成垃圾回收的工作,但是它不能保证,即也可能完不成(比如我们设置了太小的停顿时间,新生代太大等)。
那么G1怎么满足用户的期望呢?就需要停顿预测模型了。G1根据这个模型统计计算出来的历史数据来预测本次收集需要选择的堆分区数量(即选择收集哪些内存空间),从而尽量满足用户设定的目标停顿时间。如使用过去10次垃圾回收的时间和回收空间的关系,根据目前垃圾回收的目标停顿时间来预测可以收集多少的内存空间。比如最简单的办法是使用算术平均值建立一个线性关系来预测。如过去10次一共收集了10GB的内存,花费了1s,那么在200ms的停顿时间要求下,最多可以收集2GB的内存空间。G1的预测逻辑是基于衰减平均值和衰减标准差。
衰减平均 (Decaying Average)是一种简单的数学方法,用来计算一个数列的平均值,核心是给近期的数据更高的权重,即强调近期数据对结果的影响。衰减平均计算公式如下所示:
式中α为历史数据权值,1-α为最近一次数据权值。即α越小,最新的数据对结果影响越大,最近一次的数据对结果影响最大。不难看出,其实传统的平均就是α取值为(n-1)/n的情况。
同理,衰减方差的定义如下:
停顿预测模型是以衰减标准差为理论基础实现的,代码如下所示:
hotspot/src/share/vm/gc_implementation/g1/g1CollectorPolicy.hpp double get_new_prediction(TruncatedSeq* seq) { return MAX2(seq->davg() + sigma() * seq->dsd(), seq->davg() * conf idence_factor(seq->num())); }
在这个预测计算公式中:
·davg表示衰减均值。
·sigma()返回一个系数,来自G1ConfidencePercent(默认值为50,sigma为0.5)的配置,表示信赖度。
·dsd表示衰减标准偏差。
·confidence_factor表示可信度相关系数,confidence_factor当样本数据不足时(小于5个)取一个大于1的值,并且样本数据越少该值越大。当样本数据大于5时confidence_factor取值为1。这是为了弥补样本数据不足,起到补偿作用。
·方法的参数TruncateSeq,顾名思义,是一个截断的序列,它只跟踪序列中最新的n个元素。在G1 GC过程中,每个可测量的步骤花费的时间都会记录到TruncateSeq(继承了AbsSeq)中,用来计算衰减均值、衰减变量、衰减标准偏差等,代码如下所示:
hotspot/src/share/vm/utilities/numberSeq.cpp void AbsSeq::add(double val) { if (_num == 0) { // 初始时,还没有历史数据,davg就是当前参数,dvar设置为0 _davg = val; _dvariance = 0.0; } else { _davg = (1.0 - _alpha) * val + _alpha * _davg; double diff = val - _davg; _dvariance = (1.0 - _alpha) * diff * diff + _alpha * _dvariance; } }
这个add方法就是上面两个衰减公式的实现代码。其中_davg为衰减均值,_dvariance为衰减方差,_alpha默认值为0.7。G1的软实时停顿就是通过这样的预测模型来实现的。