【JVM系列】图解CMS垃圾回收器的核心流程
欢迎关注,分享更多原创技术内容~
微信公众号:ByteRaccoon、知乎:一只大狸花啊、稀土掘金:浣熊say
微信公众号海量Java、数字孪生、工业互联网电子书免费送~
CMS概述
CMS(Concurrent Mark-Sweep)收集器是 Java 虚拟机中的一种老年代(Old Generation)垃圾收集器,它主要目标是减少垃圾收集时的应用程序停顿(STW)时间。
CMS 使用并发(Concurrent)的方式来执行垃圾收集,使用的是"标记-清理"算法,尽量减少在垃圾收集过程中应用程序的暂停时间。适用于对服务响应速度要求较高的场景,例如互联网站或B/S系统的服务端Java应用。
CMS垃圾收集器作为一款专注一降低停顿时间的垃圾回收器,其主要特点是低停顿和并发:
-
并发
CMS被设计为一款并发低停顿的垃圾收集器,使得在垃圾回收过程中用户线程不会停顿或停顿时间很短,有助于保障系统的响应速度。
-
低停顿
由于其并发特性,CMS的停顿时间相对较短,适用于对服务响应速度要求高的应用场景。
但是,由于CMS的实现机制,也存在着下面这些问题:
-
对CPU资源敏感:
CMS对CPU资源敏感,因为在并发阶段虽然不会导致用户线程停顿,但会占用一部分线程(CPU资源),可能导致应用程序整体变慢,降低总吞吐量。
-
无法处理浮动垃圾:
由于CMS在并发清理阶段用户线程仍在运行,新的垃圾可能在标记过程之后(重新标记之后)的"并发清理阶段"产生。因为在并发清理阶段用户线程和GC线程是并发运行的,而CMS不能在当前收集中处理这部分浮动垃圾。
所以CMS收集器必须预留一部分空间给用户用户线程使用,不能等到老年代占用100%再进行收集。这个预留给用户的空间可以通过-XX:CMSInitiatingOccupancyFraction和-XX:+UseCMSInitiatingOccupancyOnly 两个参数来设置,这个值在JDK 5之前一般是68%,在JDK 6之后一般是92%。
因此,由于浮动垃圾的产生可能会造成垃圾并发清理过程中无法分配对象的问题,从而导致"Concurrent Mode Failure"失败,触发另一次Full GC,此时CMS会临时启用Serial Old来重新进行垃圾回收,导致停顿时间更长。
-
对CPU数量要求较高
CMS默认启动的回收线程数为(CPU数量+3)/4,当CPU不足4个时,可能对用户程序影响较大。
-
内存碎片问题
基于"标记-清除"算法的CMS会导致大量空间碎片的产生,可能对大对象分配的时候可能会产生Full GC,因为可能出现老年代空间虽有剩余但无法找到足够大连续空间来分配当前对象。
CMS收集器整体流程
CMS垃圾收集器是一种并发执行的垃圾回收器,其执行过程分为初始标记、并发标记、重新标记和并发清理等过程,主体如下:
初始标记(CMS initial mark)
初始标记只是标记GC Roots能直接关联到的对象,但需要"Stop The World"停顿,即在此期间暂停所有应用线程。这个过程在JDK 7 之前是单线程(因为GC Roots直接关联的对象相对较少),JDK 8之后是多线程的方式进行初始标记。
由于GC Root直接关联的对象小,因此可以快速的将这些对象标记出来,以减少"Stop The World"的时间。如上图所示,只标记了处于栈或者其它地方的GC Root直接关联的对象A、B、C,将这些对象标记成为存活。
并发标记(CMS concurrent mark):
如上图所示,并发标记阶段主要是进行GC Roots Tracing,即从GC Roots出发标记上所有和GC Root相连的存活对象,如上图的D、G、H;而剩下的未被标记的对象就是需要进行垃圾回收的对象,如上图的E、F、I、 J。这一过程是和用户线程并发执行的,不需要"Stop The World",因此该阶段对系统整体的性能影响较小。
但是,由于这个阶段GC线程和用户线程是并发执行的,因此可能在并发标记阶段会产生新的对象,需要进一步重新标记,来确认在并发标记阶段新引入的垃圾。
重新标记(Final remark)
由于在并发标记阶段,用户线程还是在工作的,因此有可能会产生新的对象,新对象主要通过以下三个途径产生:
-
年轻代对象晋升到老年代,可能产生新的存活对象;
-
大对象直接被分配到老年代,可能产生新的存活对象;
-
老年代和年轻代对象的引用关系发生变化;
这些新的对象可能与GC Roots相连是存活对象,如K、M;也可能不与GC Roots相连接,是垃圾对象,如N。JVM会通过Card(卡片)的方式将发生变化的老年代区域标记为"脏"区域,也就是所谓的卡片标记(Card Marking)来对新增对象的存活状态进行重新标记。
由于这个阶段由于需要确定最终的GC视图,需要避免该阶段再引入新的垃圾,因,此需要"Stop The World"停顿。与并发标记相比,停顿时间相对较短,主要关注标记发生变化的对象。
并发清除(CMS concurrent sweep):
最后,GC线程会清除不再被引用的对象,并回收他们占用的内存空间。由于前面的标记阶段已经将还在使用的对象标记了出来,因此该过程与用户线程并发执行,不需要全局停顿("Stop The World"),整个垃圾回收过程完成。
总结
本文介绍了CMS垃圾回收器的优缺点和核心时间流程,作为一款旨在降低垃圾收集停顿时间的垃圾回收器,CMS尽量减少了需要"Stop The World"的垃圾标记阶段,对于垃圾标记工作量最大的部分采用了并发标记的方式来降低系统停顿时间。
CMS垃圾收集器的主要流程包括:初始标记、并发标记、最终标记和并发清理,其中初始标记和最终标记需要STW但是速度都很快,尽量降低了系统的停顿时间。并发标记和并发清理等耗时较长的阶段采用了并发的方式,来减少系统暂停。但是,同样因为在并发清理阶段用户线程并没有停止工作,所能可能产生浮动垃圾以及可能会降级成为Serial Old垃圾回收器。
虽然,CMS是一款历史比较悠久的垃圾回收器,但是目前很多公司仍然在使用,所以对于其原理我们必须更加深入的去了解。