CMS垃圾收集器
GC类型
- Minor GC ,发生在年轻代GC
- Major GC,发生老年代GC
- Full GC,全堆垃圾回收,如MetaSpace区引起的年轻代和老年代的回收
CMS简述
1.CMS全称是Mostly Concurrent Mark and Sweep Garbage Collector
2.年轻代使用copy(Eden from to区域)算法,而老年代使用Mark-sweep方法。
3.可以看到,老年代比起Mark-Sweep多了并发操作记录。
4.CMS致力于避免老年代GC时出现长时间卡顿(但它并不是一个老年代回收器)。如果不希望长时间卡顿,同时CPUS资源标记丰富,使用CMS较合适。
5.CMS使用的是Sweep而不是Compact(内存整理算法),主要问题是内存碎片化。随着jvm长时间运行,碎片化越来越重,只有通过Full GC才能完成
6.CMS 效率提高得利于把最耗时的工作(Mark),做成了和应用线程并行。
CMS 回收过程
标记过程
初始标记(initial Mark)
- STW,标记GCRoots 直接关联的对象
初始标记阶段,只标记直接关联GC Root对象,不用向下追溯。因为最耗时时间就在追溯阶段(tracing)阶段,这样极大缩短初始标记时间。 该过程是STW,用户线程无法进行工作。但只标记第一层,所以速度很快。 该过程除了标记相关的GC Roots之外,还要标记年轻代中对象的引用,这也是CMS老年代回收,依然要扫描年轻代的原因。
- 图示
并发标记(concurrent Mark)
- 追踪与GC Roots间接关联的所有可达对象
在初始标记基础上,进行并发标记。 该步骤主要是tracing过程,用于标记所有可达对象
- 标记进程与用户线程并行
持续时间较长,但可以和用户线程并行。 该过程会会产生诸多变化: 1.有些对象,从新生代晋升到老年代(通过age=阈值 提升到老年代) 2.有些对象,直接分配到了老年代(分配担保) 3.老年代或者新生代对象引用发生变化(通过卡片记录老年代对新生代对象的引用,引用则是脏页)
- 图示
清理过程
并发预清理
- 非STW预清理
并发地预清理,也不需STW,目的让每次重新进行的初始标记阶段的STW时间尽可能缩短。 该阶段,老年代中被标记dirty的卡页,就会被重新标记,然后就会被清除dirty状态 该阶段也可以是并发的的,在执行过程中引用关系依然会发生一些变化。可以假定这个清理动作是第一次清理。因此,重新标记阶段,有可能仍然有处于dirty状态。
并发可取消的预清理
- 可选、可终止的并发预清理
重新标记需要STW,所以会有很多次的预清理动作。 并发可取消的预清理,顾名思义,在满足某些条件时,可以终止,比如迭代次数、有用工作量、消耗的系统实际等 该阶段是可选的,是”并发预清理”的阶段的一种优化 该阶段目的是,避免扫描年轻代的大量对象(标记动作需要扫描年轻代);当满足最终标记的条件时,自动退出。 标记动作是需要扫描年轻代。若年轻代对象过多,肯定严重影响标记效率,如果在此之前能够进行一次minor GC,情况会有所改善。CMS提供了参数CMSScavengeBeforeRemark,可以在进入重新标记之前强制进行一次Minor GC. GC停顿不分年轻代和年老代。设置CMSScavengeBeforeRemark,可能会在一个比较长的Minor GC之后,紧接一个CMS的Remark,他们都是STW的。 类似CMSScavengeBeforeRemark非常多的配置参数,属于jdk提供通用场景最佳配置,一般不会修改
最终标记(Final Remark)
- 二次STW对老年代存活对象标记
1.通常CMS会尝试在年轻代尽可能空的情况下运行Final Remark,以免连续多次发生STW事件 2.该阶段是CMS GC的第二次STW阶段,目的完成老年代所有存活对象标记。之前多轮preclean阶段,一直在和应用线程相互追赶,有可能跟不上引用的变化速度。本轮标记动作需要STW来处理这个问题 3.如果预处理做的做的不好,会显著增加Final Remark的STW时间 4.从该处看到CMS将垃圾回收分了多个部分,而影响最大的不是STW本身,而是它之前预处理动作(效果不好,会显著增加二阶段Final Remark STW时间)
并发清除(Concurrent Sweep)
- GC线程和应用线程并发清除垃圾
1.该阶段,用户线程被重新激活,目标是删除不可达对象,回收器空间 2.由于CMS并发清理阶段和用户线程并发执行,伴随程序运行自然会有新的垃圾,CMS无法在当次GC中处理掉,只好待下次GC时进行清理,这部分垃圾成为”浮动垃圾”
- GC 与应用线程一起运行,GC垃圾时产生浮动垃圾
并发重置(Concurrent Rest)
- 此阶段和应用线程并发执行,重置CMS算法相关内部数据,为下次GC循环做准备
内存碎片
- CMS 老年代回收问题
1.CMS执行过程中,用户线程也在运行。这就需要保证充足内存空间提供给来用户使用。 2.如果老年代空间接近占满,再启动该回收过程,用户线程会产生”concurrent Mode Failure”错误,这是临时启用Serial old收集器来重新对老年代垃圾收集,那么停顿(STW)时间会更惨。 3.为避免老年代空间占满,老年代空间需要预留30%,大概能用的只有70%。使用-XX:CMSInitiatingOccupancyFraction用来配置这个比例(首先开启参数UseCMSInitiatingOccupancyOnly).也就是说,当老年代使用达到70%,就会触发GC,不会造成程序报错,影响运行。如果老年代增长不太快,可以调高该参数,降低内存回收次数。 4.老年代内存比率不太好设置,一般堆大小小于2GB时,都不会考虑CMS垃圾收集器。 5.CMS对老年代回收时,并未进行内存整理。这会造成内存运行过久后,产生大量碎片。如申请稍大对象空间,就会引起分配失败 6.CMS提供两个参数解决该内存碎片问题 (1)UseCMSCompactAtFullCollect(默认开启),FullGC时,进行内存碎片整理。内存整理无法并发,所以停顿会较长 (2)CMSFullGCBeforeCompaction,每隔多少次不压缩的Full GC后,执行一次带压缩的Full GC。默认为0,表示每次进入Full GC都有进行碎片整理。 7.预留内存空间加上内存碎片,使用CMS垃圾回收器的老年代,留给使用的空间不是太多,也是CMS的一个弱点。 ````
- 老年代可用空间
小结
-
CMS 垃圾收集四阶段
1.初始标记(initial mark) 2.并发标记(GC,应用并发执行) 3.重新标记(Final Remark,STW) 4.并发清理(cocurrent sweep)
-
引起STW的阶段
1.初始标记,GC Roots直接对象标记,部分停顿时间较短。 2.Minor GC(并发可取消预清理,可选),在预处理阶段对年轻代的回收,STW由年轻代决定。 3.重新标记,由于preclean阶段介入,该部分停顿也较短 4.Serial old收集老年代的停顿,发生在预留空间不足的情况下,时间持续较长 5.Full GC,永久代空间耗尽时的操作,由于会有整理阶段,时间加长。
-
发生GC是,确定发生在哪个阶段,对症下药,GC log通常有非常详细的通常
-
CMS trade-off
优点:低延迟,对大堆来说。大部分垃圾回收过程并发执行 缺点: 1.内存碎片。需要Full GC 进行内存整理,会造成较长卡顿 2.需要为用户正常使用时,要求老年代预留空间,防止内存占满,影响用户体验,分配收集阶段产生的”浮动垃圾” 3.使用更多CPU资源,应用运行的同时进行堆扫描。