简介
CMS:Concurrent Mark Sweep,以获取最短停顿时间为目标的垃圾回收期。看其全名就知道使用了标记-清除算法,老年代垃圾回收器。除了CMS,其他都不单独针对老年代进行GC
GC步骤
- 初始标记,STW,标记 GC Roots 能直接关联到的对象,很快
- 并发标记,从 GC Roots 直接关联的对象向下遍历对象图,很慢,但是能和用户线程并发进行
- 重新标记,STW,修正并发标记时的引用变化,比初始标记时间长,但也很短
- 并发清除
实际上能和用户线程并发执行的垃圾收集器应该大致都是这几个过程,G1也差不多,不过细节不同。
主要优点:并发收集、低停顿
问题
CMS无法清理浮动垃圾(Floating Garbage),有可能出现"Concurrent Mode Failure"从而导致一次完全STW的Full GC。
CMS并发标记和并发清理期间和用户线程并发进行,于是这段时间仍可能产生和垃圾,这部分垃圾就是浮动垃圾,对于CMS来说需要下一次GC才能清理。
同样的,这段时间也可能产生新对象,于是需要预留一部分空间给用户线程使用,因此CMS不能等到老年代几乎被填满再进行GC,必须预留一部分。
JDK5默认老年代使用率68%被激活,JDK6提升到92%。这导致另一种风险,就是并发执行时内存不足以给用户线程分配使用,则导致一次并发失败"Concurrent Mode Failure",则启动后备预案,冻结用户线程,临时启用 Serial Old进行全程STW的老年代垃圾清理
CMS采用标记清除算法,有内存碎片问题,于是可能有大量剩余空间但是分配大对象失败,从而不得不提前执行 Full GC,解决方法是一段时间后进行内存碎片整理。
参数
-XX:+CMSInitiatingOccupancyFraction=70:设置老年代使用率达%x触发CMS
-XX:+UseCMSInitiatingOccupancyOnly=false:只是用设定的回收阈值(上面指定的70%),如果不指定,JVM仅在第一次使用设定值,后续则自动调整.
-XX:+UseCMSCompactAtFullCollection=true:CMS不得不进行Full GC时进行碎片整理,默认true,JDK9废弃(注意CMS的GC不是Full GC)
-XX:+CMSFullGCsBeforeCompaction=0:CMS进行N次不整理碎片的Full GC后,在下一次进行Full GC前进行碎片整理,默认-1表示每一次进行前进行碎片整理
-XX:+CMSScavengeBeforeRemark=false:在CMS GC前启动一次ygc,目的在于减少old gen对ygc gen的引用,降低remark时的开销-----一般CMS的GC耗时 80%都在remark阶段
其他
CMSInitiatingOccupancyFraction参数
原文:-XX:CMSInitiatingOccupancyFraction - 简书 (jianshu.com)使用例子:-XX:CMSInitiatingOccupancyFraction=70
CMS垃圾收集器,当老年代达到70%时,触发CMS垃圾回收。
查看CMSInitiatingOccupancyFraction的初始值为-1
win下执行
.\java.exe -XX:+PrintFlagsFinal | findStr CMSInitiatingOccupancyFraction
结果
intx CMSInitiatingOccupancyFraction = -1 {product}
那么-1代表着什么呢?
查看jvm源码可知
product(intx, CMSInitiatingOccupancyFraction, -1, \ "Percentage CMS generation occupancy to start a CMS collection " \ "cycle. A negative value means that CMSTriggerRatio is used")
注释里也说了,如果CMSInitiatingOccupancyFraction是个负值,那么CMSTriggerRatio将被用到
那么具体是如何用到的呢?
_cmsGen ->init_initiating_occupancy(CMSInitiatingOccupancyFraction, CMSTriggerRatio); void ConcurrentMarkSweepGeneration::init_initiating_occupancy(intx io, uintx tr) { assert(io <= 100 && tr <= 100, "Check the arguments"); if (io >= 0) { _initiating_occupancy = (double)io / 100.0; } else { _initiating_occupancy = ((100 - MinHeapFreeRatio) + (double)(tr * MinHeapFreeRatio) / 100.0) / 100.0; } }
如果CMSInitiatingOccupancyFraction在0~100之间,那么由CMSInitiatingOccupancyFraction决定。
否则由按 ((100 - MinHeapFreeRatio) + (double)( CMSTriggerRatio * MinHeapFreeRatio) / 100.0) / 100.0 决定。
那么MinHeapFreeRatio,CMSTriggerRatio的初始值是多少?(注:win下JDK1.8.0_202默认配置,查询MinHeapFreeRatio值为0)
uintx MinHeapFreeRatio = 40 {manageable} uintx CMSTriggerRatio = 80 {product}
CMS的GC
有一点需要注意的是:CMS并发GC不是“full GC”。HotSpot VM里对concurrent collection和full collection有明确的区分。所有带有“FullCollection”字样的VM参数都是跟真正的full GC相关,而跟CMS并发GC无关的。
CMSFullGCsBeforeCompaction这个参数在HotSpot VM里是这样声明的:
product(bool, UseCMSCompactAtFullCollection, true, \ "Use mark sweep compact at full collections") \ \ product(uintx, CMSFullGCsBeforeCompaction, 0, \ "Number of CMS full collection done before compaction if > 0") \
然后这样使用的
*should_compact = UseCMSCompactAtFullCollection && ((_full_gcs_since_conc_gc >= CMSFullGCsBeforeCompaction) || GCCause::is_user_requested_gc(gch->gc_cause()) || gch->incremental_collection_will_fail(true /* consult_young */));
CMS GC要决定是否在full GC时做压缩整理(碎片整理),会依赖几个条件。其中,
- 第一种条件,UseCMSCompactAtFullCollection 与 CMSFullGCsBeforeCompaction 是搭配使用的;前者目前默认就是true了,也就是关键在后者上。
- 第二种条件是用户调用了System.gc(),而且DisableExplicitGC没有开启。
- 第三种条件是young gen报告接下来如果做增量收集会失败;简单来说也就是young gen预计old gen没有足够空间来容纳下次young GC晋升的对象。
上述三种条件的任意一种成立都会让CMS决定这次做full GC时要做压缩。
CMSFullGCsBeforeCompaction 说的是,在上一次CMS并发GC执行过后,到底还要再执行多少次full GC才会做压缩。默认是0,也就是在默认配置下每次CMS GC顶不住了而要转入full GC的时候都会做压缩。 把CMSFullGCsBeforeCompaction配置为10,就会让上面说的第一个条件变成每隔10次真正的full GC才做一次压缩(而不是每10次CMS并发GC就做一次压缩,目前VM里没有这样的参数)。这会使full GC更少做压缩,也就更容易使CMS的old gen受碎片化问题的困扰。 本来这个参数就是用来配置降低full GC压缩的频率,以期减少某些full GC的暂停时间。CMS回退到full GC时用的算法是mark-sweep-compact,但compaction是可选的,不做的话碎片化会严重些但这次full GC的暂停时间会短些;这是个取舍。
-XX:CMSInitiatingOccupancyFraction=70 和-XX:+UseCMSInitiatingOccupancyOnly
这两个设置一般配合使用,一般用于『降低CMS GC频率或者增加频率、减少GC时长』的需求
-XX:CMSInitiatingOccupancyFraction=70 是指设定CMS在对内存占用率达到70%的时候开始GC(因为CMS会有浮动垃圾,所以一般都较早启动GC);
-XX:+UseCMSInitiatingOccupancyOnly 只是用设定的回收阈值(上面指定的70%),如果不指定,JVM仅在第一次使用设定值,后续则自动调整.
-XX:+CMSScavengeBeforeRemark
在CMS GC前启动一次ygc,目的在于减少old gen对ygc gen的引用,降低remark时的开销-----一般CMS的GC耗时 80%都在remark阶段