• 极客专栏正式上线!欢迎访问 https://www.jikewenku.com/topic.html
  • 极客专栏正式上线!欢迎访问 https://www.jikewenku.com/topic.html

CMS发生的3种GC概念解惑

技术杂谈 勤劳的小蚂蚁 2个月前 (02-09) 78次浏览 已收录 0个评论 扫描二维码

在G1出来之前,CMS绝对是OLTP系统的标配。即使G1出来几年了,生产环境很多的JVM实例还是采用ParNew+CMS的组合。但是即使其得到这么广泛的应用,还是有很多同学对它有很深的误解。本文主要对ParNew+CMS经典组合下,触发的几种垃圾回收方式进行几个概念的纠正。

Backgroud CMS

可能更多人只知道CMS,而不知道Backgroud CMS。事实上我们说的CMS,即包含了5个阶段的CMS,就是Background CMS,如下图所示:
CMS示意图
说明
  • 图中初始化标记阶段是串行的,这是JDK7的行为。JDK8以后默认是并行的,可以通过参数-XX:+CMSParallelInitialMarkEnabled控制。
  • 由图可知,CMS还有两个阶段是完全STW(Stop The World)的,即初始化标记和最终标记(重新标记)。
  • 其他阶段都是并发的,所以CMS被称为Concurrent Mark&Sweep,但是我认为前面还需要加个Mostly才是最贴切,即CMS是一个Mostly Concurrent Mark and Sweep Garbage Collector,因为它还没办法做到完全并发。
不只是CMS,就是G1,以及JDK11的ZGC都没有做到完全的并发。就目前笔者了解到的所有GC中,只有Azul的C4是完全并发的。
为什么有个Background关键词?我们都知道配置CMS垃圾回收的话,有两个重要参数:-XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccupancyOnly,这两个参数表示只有在Old区占了75%的内存时才满足触发CMS的条件。注意这只是满足触发CMS GC的条件。至于什么时候真正触发CMS GC,由一个后台扫描线程决定。CMSThread默认2秒钟扫描一次,判断是否需要触发CMS,这个参数可以更改这个扫描时间间隔,例如-XX:CMSWaitDuration=5000,此外可以通过jstack日志看到这个线程:
“Concurrent Mark-Sweep GC Thread” os_prio=2 tid=0x000000001870f800 nid=0x0f4 waiting on condition

Foregroud CMS

这个名词第一次听笨神说的(公众号:你假笨)。当然笨神也不是随便自己捏造一个名词出来,这个名词来自于openjdk源码,参考concurrentMarkSweepGeneration.cpp
void CMSCollector::collect_in_foreground(bool clear_all_soft_refs, GCCause::Cause cause) {
    case Resizing: {
        // Sweeping has been completed; the actual resize in this case
        // is done separately; nothing to be done in this state.
        _collectorState = Resetting;
        break;
    }  
    case Precleaning:
    case AbortablePreclean:
        // Elide the preclean phase
        _collectorState = FinalMarking;
        break;
    default:
        ShouldNotReachHere();

}

源码比较多,我就不全部贴出来的,有兴趣的同学可以自己下载源码查看。
它发生的场景,比如业务线程请求分配内存,但是内存不够了,于是可能触发一次CMS GC,这个过程就必须要等待内存分配成功后线程才能继续往下面走,因此整个过程必须STW,因此这种CMS GC整个过程都是暂停应用的,但是为了提高效率,它并不是每个阶段都会走的,只走其中一些阶段,通过上面的源码可知,这些省下来的阶段主要是并行阶段:Precleaning、AbortablePreclean,Resizing。但不管怎么说如果走了类似foreground这种CMS GC,那么整个过程业务线程都是不可用的,效率会影响挺大。

MSC

MSC的全称是Mark Sweep Compact,即标记-清理-压缩,MSC是一种算法,是ParNew+CMS发生FullGC时采用的垃圾回收算法。它和CMS最大的不同是,它会Compact压缩,而无论是Background CMS和Foreground CMS都不会压缩堆,这也是CMS GC算法最让人诟病的地方:标记清理算法会导致内存碎片问题,从而埋下发生FullGC导致长时间STW的隐患。
如果触发了FullGC,那就是ParNew+CMS组合最糟糕的情况,整个过程单线程,完全STW,还会压缩堆(是否压缩堆可以通过参数控制),真的不能再糟糕了!想象如果这时候业务量比较大,由于FullGC导致服务完全暂停几秒钟,甚至上10秒,对用户体验影响得多大。
另外,别以为G1就好很多,G1的FullGC同样是垃圾级别的存在:
The G1 garbage collector is designed to avoid full collections, but when the concurrent collections can’t reclaim memory fast enough a fall back full GC will occur. The current implementation of the full GC for G1 uses a single threaded mark-sweep-compact algorithm.
这段原话出自:http://openjdk.java.net/jeps/307
有办法避免么?有!笔者给你分享一个歪门邪道,不记得是多少年前,在哪里道听途说才得到这个偏方的,而且据说以前阿里的一些业务也用了这个偏方,不管是哪里得来的偏方,反正肯定有用的。这个偏方很简单:在业务最低峰期(比如大陆的很多业务可以选在凌晨2,3点夜深人静的时候执行)强行触发FullGC并结合参数-XX:+UseCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompaction=0(这两个参数默认值就是这样的,表示触发FullGC时压缩堆),从而优化内存碎片并压缩堆,降低在业务高峰期发生MSC的概率。
可能还有一小部分同学连强行触发FullGC都不知道,笔者好人做到底,送佛送到西:
# 没有开启-XX:+DisableExplicitGC的前提下调用System.gc()就会发生FullGC
System.gc();

或者通过jmap命令触发:
jmap -histo:live pid




    丨极客文库, 版权所有丨如未注明 , 均为原创丨
    本网站采用知识共享署名-非商业性使用-相同方式共享 3.0 中国大陆许可协议进行授权
    转载请注明原文链接:CMS发生的3种GC概念解惑
    喜欢 (0)
    [247507792@qq.com]
    分享 (0)
    勤劳的小蚂蚁
    关于作者:
    温馨提示:本文来源于网络,转载文章皆标明了出处,如果您发现侵权文章,请及时向站长反馈删除。

    您必须 登录 才能发表评论!

    • 精品技术教程
    • 编程资源分享
    • 问答交流社区
    • 极客文库知识库

    客服QQ


    QQ:2248886839


    工作时间:09:00-23:00