Java虚拟机 ----CMS 垃圾回收器

一、CMS 详解

  CMS(Concurrent Mark Sweep)回收器是在最短回收停顿时间为前提的回收器,属于多线程回收器,采用标记-清除算法。

特点:基于标记-清除算法实现。并发收集、低停顿。

应用场景:适用于注重服务的响应速度,希望系统停顿时间最短,给用户带来更好的体验等场景下。如web程序、b/s服务。

相比之前的回收器,CMS 回收器的运作过程比较复杂,分为四步:

  1、初始标记:标记GC Roots能直接到的对象。速度很快但是仍存在Stop The World问题。

  2、并发标记:进行GC Roots Tracing 的过程,找出存活对象且用户线程可并发执行。

  3、重新标记:为了修正并发标记期间因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录。仍然存在Stop The World问题。

  4、并发清除:对标记的对象进行清除回收。

 CMS收集器的内存回收过程是与用户线程一起并发执行的。

 CMS收集器的工作过程图:

                   Java虚拟机 ----CMS 垃圾回收器

      初始标记(CMS initial mark)和 重新标记(CMS remark)会导致 用户线程 卡顿,Stop the World 现象发生。

CMS收集器的缺点

    对CPU资源非常敏感
        CMS回收器过分依赖于 多线程环境,默认情况下,开启的 线程数 为(CPU 的数量 + 3)/ 4,当 CPU 数量少于 4 个时,CMS 对 用户查询 的影响将会很大,因为他们要分出一半的运算能力去 执行回收器线程;

    无法处理浮动垃圾,可能出现Concurrent Model Failure失败而导致另一次Full GC的产生。
因为采用标记-清除算法所以会存在空间碎片的问题,导致大对象无法分配空间,不得不提前触发一次Full GC。

二 、CMS常用参数

 -XX:+UseConcMarkSweepGC 该标志首先是**CMS收集器。默认HotSpot JVM使用的是并行收集器。                                   

 -XX:ParallelCMSThreads=20 设定其中ParallelGCThreads是年轻代的并行收集线程数CMS默认启动的回收线程数目是                    (ParallelGCThreads + 3)/4) 。

-XX:CMSInitiatingOccupancyFraction=80 : 手动指定当老年代已用空间达到80%时,触发老年代回收(默认92%)

-XX:+DisableExplicitGC : 手动配置禁止使用外部调用System.gc 来进行触发垃圾回收

-XX:MaxGCPauseMillis=100这个参数用于设置GC暂停等待时间,单位为毫秒,不要设置过低一般在100-200ms

-XX:+UseCMSCompactAtFullCollection : 在进行Full GC时对内存进行压缩

 -XX:+CMSScavengeBeforeRemark  在CMS GC前启动一次ygc,目的在于减少old gen对ygc gen的引用,降低remark时的开销-----一般CMS的GC耗时 80%都在remark阶段

-XX:CMSFullGCsBeforeCompaction=2 : 与-XX:+UseCMSCompactAtFullCollection 关联使用标识着每经过多少次Full GC 触发对内存进行一次压缩,默认是0次.

-XX:+CMSClassUnloadingEnabled : 手动指定CMS 收集器对非堆区域永久代进行回收,默认永久代不回收

三、常见问题

1、promotion failed – concurrent mode failure

Minor GC后, Survivor空间容纳不了剩余对象,将要放入老年代,老年代有碎片或者不能容纳这些对象,就产生了concurrent mode failure, 然后进行stop-the-world的Serial Old收集器。

解决办法:-XX:UseCMSCompactAtFullCollection -XX:CMSFullGCBeforeCompaction=5 或者调大新生代或者Survivor空间

2、concurrent mode failure

CMS是和业务线程并发运行的,在执行CMS的过程中有业务对象需要在老年代直接分配,例如大对象,但是老年代没有足够的空间来分配,所以导致concurrent mode failure, 然后需要进行stop-the-world的Serial Old收集器。

解决办法:+XX:CMSInitiatingOccupancyFraction,调大老年带的空间,+XX:CMSMaxAbortablePrecleanTime

3、碎片整理

-XX:+UseCMSCompactAtFullCollection  强制进行空间碎片整理
CMS 采用标记算法,会产生大量的空间碎片。以上参数就是强制执行一次空间碎片整理,但是空间碎片整理会引发STW。
-XX:+CMSFullGCsBeforeCompaction=10  经过10次FGC后进行空间碎片整理,以降低STW次数

四、cms 垃圾回收实际7个步骤

4.1 、Phase1 :Initial Mark【初始标记】

     这个是CMS两次stop-the-world事件的其中一次,这个阶段的目标是:标记那些直接被GC root引用或被年轻代存活对象所引用所有对象。用图来表示:

Java虚拟机 ----CMS 垃圾回收器

上面有对象是直接被GC ROOTS所指用的,有些对象是被年轻代引用的,都会被标记出来。

4.2 Phase2 : Concurrent Mark 【并发标记】

      在这个阶段Garbage Collector会遍历老年代,然后标记所有存活的对象,它会根据上个阶段找到GC ROOTS遍历查找。并发标记阶段,它会与用户的应用程序并发运行。并不是老年代所有的存活对象都会被标记,因为在标记期间用户的程序可能会改变一些引用。如下图:

Java虚拟机 ----CMS 垃圾回收器
在上面的图中,与阶段1的图进行对比,就会发现有一个对象的引用已经发生了变化,如标黑的那个对象。

4.3 Phase3 : Concurrent Preclean【并发预先清除】

    这也是一个并发阶段,与应用的线程并发运行,并不会stop应用的线程。在并发运行的过程中,一些对象的引用可能会发生变化,但是这种情况发生时,JVM会将包含这个对象的区域(Card)标记为Dirty,这也就是Card Marking。在pre-clean阶段,那些能够从Dirty对象到达的对象也会被标记,这个标记做完之后,dirty card标记就会被清除了。
下面看下示意图:   
Java虚拟机 ----CMS 垃圾回收器

上图中标红的则为Dirty,而能够被它所直接到达的对象也会被标记,标记完了则dirty card标记被清除,如下:
Java虚拟机 ----CMS 垃圾回收器

4.4、Phase4 : Concurrent Abortable Preclean【并发可能失败的预先清除】

      这也是一个并发阶段,但是同样不会影响用户的应用线程,这个阶段是为了尽量承担STW(stop-the-world)中最终标记阶段的工作。这个阶段持续时间依赖于很多的因素,由于这个阶段是在重复做很多相同的工作,直接满足一些条件(比如:重复迭代的次数、完成的工作量或者时钟时间等)

4.5、Phase5 : Final Remark【最终重新标记】

       这是第二个STW阶段,也是CMS中的最后一个,这个阶段的目标是标记老年代所有的存活对象,由于之前的阶段是并发执行的,GC线程可能跟不上应用程序的变化,为了完成标记老年代所有存活对象的目标,STW就非常有必要了。
     通常CMS的Final Remark阶段会在年代代尽可能干净的时候运行,目的是为了减少连续STW发生的可能性(年轻代存活对象过多的话,也会导致老年代涉及的存活对象会很多)。这个阶段会比前面的几个阶段更复杂一些。

标记阶段完成
       经历过以上五个阶段之后,老年代所有存活的对象都被标记过了,现在可以通过清除算法去清理那些老年代不再使用的对象。可见其实CMS是将一个标记阶段细分成五个子阶段了。

4.6 、Phase6 : Concurrent Sweep【并发清除】

    这里不需要STW,它是与用户的应用程序并发运行,这个阶段是:清除那些不再使用的对象,回收它们的占用空间为将来使用,如图:
Java虚拟机 ----CMS 垃圾回收器

4.7、Phase7 : Concurrent Reset【并发重置】

这个阶段也是并发执行的,它会重设CMS内部的数据结构,为下次的GC做准备。