Golang分代GC的策略
source link: https://studygolang.com/articles/20159?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
序
上一篇文章 中讲解了Go分代GC的实现思路,还有一个问题没有讲解,Go中分代GC(Garbage Collection)的策略,如何穿插使用 Minor GC
和 Major GC
?
为何要穿插使用?
file
图片仅用作说明,有些地方不是很严谨,也与Go的GC方式不完全相同。
因为每轮GC都会有新的存活对象,存活下来的对象即被认为是老年代对象,这些对象在执行 Minor GC
期间是不会被清扫的,这也就会导致使用的堆内存会越来越大。经过几轮GC之后有些老年代对象可能已经不可达,可以被回收了,这时就要进行 Major GC
来进行完整的清扫,不论老年代对象和新生代对象,只要不再存活,就会被清扫回收掉,达到释放内存的目的。 上一篇文章 也有说到, Go将分代GC算法与标记清扫算法相结合,实现了一个不移动的分代GC ,究竟是如何结合使用的,就是本篇文章所要讲的。
如何进行穿插使用
下面的代码就是是判断本轮GC执行 Minor GC
或 Major GC
判断的函数,当函数返回 true
时,执行 Full GC
( Major GC
),返回 false
时,执行 Gen GC
( Minor GC
)。
func isGCCycleFull() bool { if !gcGen { return true } if gen.spaceFull || gen.forceFullGC { return true } // If full GC are taking a reasonable amount of time do not // bother with a generational GC. percentGCTime := int(memstats.gc_cpu_fraction * 100) if percentGCTime < gen.highCostThreshold { return true } // The next cycle will be full during warmup. // Perhaps this should trigger as long as we are initializing which we can // defined as a monotonic increase in heap size. if gen.cycleCount < gen.warmupCount { // number of warmup GC to do before considering a generational GC. return true } if gen.countBased { if gen.cycleCount%gen.cycleModulus == 0 { return gen.countBasedGen } else { return !gen.countBasedGen } } if work.fullRunCount >= work.fullSwitchCount { // Do a generational GC at least every switchCount GCs. return false } // Consider heuristics based on previous mark / cons ratios. if work.fullMarkConsEWMA*1.1 < work.genMarkConsEWMA { // This forces a generational GC as soon as warmup is over since work.genMarkConsEWMA is 0 return true } // Do a generational GC in the next cycle. return false }
下面分段解释一些这个函数,深入分析一下Go 的分代GC策略
-
gcGen
是一个const
常量,表示是否开启分代GC模式,当未开启时和原生GC无区别,每次都是执行Full GC
。
if !gcGen { return true }
-
spaceFull
是标记本轮Gen GC
中存活的对象空间 与 上一次Full GC
中释放的空间的一半 的大小关系,在一轮Full GC
后可能有连续几次的Gen GC
,这里的Full GC
是指最近的一次Full GC
。当本轮Gen GC
中存活的对象空间大于上一次Full GC
中释放的空间的一半时,spaceFull
就会被置成true
;forceFullGC
在用户手动调用runtime.GC
时会被置成true
。
if gen.spaceFull || gen.forceFullGC { return true }
-
percentGCTime
是GC所占CPU时间的百分比,即gctrace
中的百分数,下面数据中的6%,highCostThreshold
在不开启调试模式,程序正常运行时的初始值为10,不会改变,即GC所占CPU时间的百分比小于10%时都不会进行Gen GC
,调试模式中highCostThreshold=1
。gc 24 @1.583s 6%: 0.014+33+0.008 ms clock, 0.11+0/62/54+0.069 ms cpu, 158->158->316 MB, 316 MB goal, 8 P
percentGCTime := int(memstats.gc_cpu_fraction * 100) if percentGCTime < gen.highCostThreshold { return true }
-
cycleCount
表示应用程序当前共进行了多少轮的GC(包含Full GC
和Gen GC
),warmupCount
目前的初始值为4,不会改变。这条规则表示的意思是,在前4轮GC不要进行Gen GC
,因为前几轮对象较少,堆内存也较小,进行Gen GC
可能不会有收益,可以理解为在进行初始化为Gen GC
做准备。
if gen.cycleCount < gen.warmupCount { // number of warmup GC to do before considering a generational GC. return true }
-
countBase
的初始值目前是false
,countBasedGen
的初值目前是false
,cycleModulus
的初始值目前为2,不会改变。那么眼尖的人已经看出来了,这个分支没有用啊,countBase
一直为false
,那么这个分支就永远不会走。这个分支可以理解为用来强制触发Gen GC
来进行测试和实验数据获取的。试想一下如果这样设置countBased=true
,cycleModulus=2
,countBasedGen=false
,那么如果可以走到这个分支,Full GC
和Gen GC
的穿插策略即是,每2轮作为一个循环,奇数轮返回true
执行Full GC
,偶数轮返回false
执行Gen GC
。
if gen.countBased { if gen.cycleCount%gen.cycleModulus == 0 { return gen.countBasedGen } else { return !gen.countBasedGen } }
-
一起说下剩下的几条规则。
fullRunCount
表示自从上一轮Gen GC
后执行了多少轮Full GC
,同样,在一轮Gen GC
后也有可能连续几次的Full GC
;fullSwitchCount
是一个在程序中会根据情况进行更新的值,初始值为0,每次更新都会+8。 介绍几个概念EWMA(指数加权移动平均值):可以简单的认为是平均值,有兴趣的可以自己查下资料深入理解下。
MarkConsEWMA:Mark是指本轮GC中的标记成本(使用的CPU时间),Con是指本轮GC中释放的内存量,MarkConsEWMA是Mark/Con这个值的EWMA,对于
Full GC
和Gen GC
分别维护一个MarkConsEWMA
。个人认为可以理解为GC的性价比,标记成本越小越好,释放的内存越多越好,所以MarkConsEWMA
这个值越小越好。如果在
Full GC
后的第一轮Gen GC
中,单轮的MarkCons
大于Full GC
的MarkConsEWMA
,或者Gen GC
的总MarkConsEWMA
大于Full GC
的总MarkConsEWMA
,即Gen GC
的收益没有Full GC
大时, 那么fullSwitchCount
将会+8,即更倾向于多做Full GC
。另外如果
Gen GC
的总MarkConsEWMA
大于Full GC
的总MarkConsEWMA
的1.1倍时,也会进行Full GC
,如果所有规则都没有匹配,默认进行Gen GC
。
if work.fullRunCount >= work.fullSwitchCount { // Do a generational GC at least every switchCount GCs. return false } if work.fullMarkConsEWMA*1.1 < work.genMarkConsEWMA { // This forces a generational GC as soon as warmup is over since work.genMarkConsEWMA is 0 return true } return false
isGCCycleFull函数返回值的更新时间点
在GC的标记完成之后,扫描开始之前的这个时间点会确定下一轮GC时 Full GC
还是 Gen GC
。
A GC cycle starts after mark is complete and sweeping is about to begin. It is at this point that the decision of whether the upcoming cycle will be generational or a full GC is made.
End
如有错误还请指出,欢迎交流讨论。
转载请注明出处,谢谢。
最后祝大家劳动节快乐。
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK