入门客AI创业平台(我带你入门,你带我飞行)
博文笔记

深入详解CMS收集器

创建时间:2018-01-07 投稿人: 谭周武的爷爷 浏览次数:906

前言

CMS收集器是一种并发收集器,它以获取最短回收停顿时间为目标的收集器。目前很大一部分java应用尤其重视服务的响应速度,希望系统停顿的时间最短,以给用户带来较好的体验。CMS收集器就符合这类需求。

注意:

并行:指多条垃圾收集并行工作,但此时用户线程处于等待状态。

并发:指用户线程与垃圾收集线程同时执行。

开启CMS收集器参数:XX:+UseConcMarkSweepGC,打开此开关后,使用ParNew+CMS+Serial Old的收集器组合进行内存回收,Serial Old收集器将作为CMS收集器出现Concurrent Mode Failure失败后的后备收集器使用

正文

CMS全称Concurrent Mark Sweep 从名字上就可以看出,CMS收集器是基于“标记——清除”算法实现的,它的运作过程分为四个步骤。

  • 初始标记

  • 并发标记

  • 并发预清理

  • 重新标记

  • 并发回收

初始标记

并发回收由”初始标记“阶段开始,这个阶段会暂停所有的应用程序线程,这个阶段的主要任务是找到堆中所有回收根节点对象,速度很快。

并发标记

这个阶段应用线程可以持续运行,不会被中断。它的工作在初始标记的基础上继续向下追溯标记,不会对堆的使用情况产生实质的改变。

并发预清理

所有应用线程进入重新标记阶段后都会被暂停,如果新生代收集刚刚结束,紧接着就是一个标记阶段的话,应用线程会遭遇2次连续的停顿操作,使用CMS预清理的目的就是希望尽量缩短停顿的长度,避免连续的停顿。此阶段所做的事情就是等到新生代空间占用到50%左右时执行一次常规的新生代收集。

重新标记

这个阶段会暂停应用线程,是为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,

并发回收

清除不可用对象,回收线程与应用线程并发运行

缺点

CMS收集器堆cpu资源比较敏感

每个CMS后台线程都会100%的占用机器上的一颗CPU

CMS无法处理浮动垃圾

由于CMS并发清理阶段用户线程还在不断运行,伴随这就会有新的垃圾不断产生,这一部分垃圾出现在标记过程后,CMS无法在当次收集中处理掉它们,只好留在下一次GC时再清理掉。

CMS会有大量空间碎片产生

CMS是一款基于“标记——清除”算法实现的收集器,这意味着收集结束会有大量空间碎片产生,空间碎片过多将会给大对象分配带来很大麻烦,往往表现在老年代有很大空间剩余,但是无法找到连续的空间来分配当前的对象,不得不提前触发一次FullGC。

-XX+UseCMSCompactAtFullCollection 在FULL GC的时候, 对年老代的压缩,这个参数默认是开启的,虚拟机设计者还提供了另外一个参数如下

-XX:CMSFullGCsBeforeCompaction 这个参数是用于设置执行多少次不压缩的Full GC后跟着来一次带压缩的Full GC,这个参数的默认值为0表示每次Full GC都进行碎片整理。

增量式CMS垃圾收集器(iCMS)

每个cms的线程都会100%占用机器上的一颗cpu,如果当cpu不足4个比如2个时,cms堆用户程序的影响就可能变得很大,如果本来CPU负载就比较大,还要分出一半的运算能力去执行收集器线程,就可能导致用户程序的执行速度忽然降低了50%,这是让人无法接受的。为了应付这种情况,虚拟机提供了一种称为“增量式并发收集器”(i-CMS)的CMS变种,所做的事情就是让GC线程,用户线程交替运行,尽量减少GC线程独占资源的时间。

iCMS依据责任周期原则给进行工作,这个原则决定了CMS垃圾收集器的后台线程妹隔多长时间扫描一次堆。责任周期的时间长度是义相邻两次新生代垃圾收集之间的时间长度计算得出的。

随着多核技术的发展,多处理器几乎成为所有系统的标准配置,这使得iCMS存在的意义变得不再那么重要,在Java8中已经不推荐使用。如果系统确实只配备了极其有限的CPU,作为替代方案,可以考虑使用G1收集器(因为G1收集器的后台线程在垃圾收集的过程也会周期性的暂停,客观上减少了与线程竞争CPU资源的情况)

常见调优

调优CMS收集器最要紧的工作就是要避免发生并发模式失效以及晋升失败。

并发模式失效

CMS不能以足够快的速度清理老年代空间,当老年代空间使用达到某个程度(例如为老年代的70%)时,并发回收就开始了,一个CMS后台线程开始扫描老年代空间,CMS收集器必须在老年代剩余的空间(30%)用尽之前完成老年代的扫描及回收。如果不能完成及时完成此时工作就会发生并发模式失效,此时Serial Old收集器将作为CMS收集器出现并发模式失效后的后备收集器使用。

当发现GC日志存在"concurrent mode failue"即为并发模式失效,(1)新生代发生垃圾回收,同时老年代又没有足够空间容纳晋升的对象时,CMS垃圾回收就会退化成Full GC。所有线程都会被暂停,老年代所有的无效对象都被回收,该操作会导致应用线程长时间停顿。(2)老年代有足够的空间可以容纳晋升的对象,但是由于空闲空间的碎片化,导致晋升失败,最终会发生Full GC

针对并发模式失效的调优

  • 增大老年代空间。

  • 给后台线程更多运行机会

  • 使用更多的后台回收线程

1、给后台线程更多运行机会

更早地启动并发收集周期,为了实现这种配置,最简单的方法是同时设置下面两个标志

-XX:CMSInitiatingOccupancyFraction=N 占用老年代N%后开始CMS收集

-XX:+UseCMSInitiatingOccupancyOnly 禁止hostspot自行触发CMS GC

默认情况下UseCMSInitiatingOccupancyOnly标志为假,CMS会使用更复杂的算法判断什么时候启动并行收集线程。如果开启此标志CMSInitiatingOccupancyFraction的默认值就会被置为70,即CMS会在老年代空间占用达到70%时启动并发回收周期。

2、调整CMS后台线程

每个CMS后台线程都会100%的占用机器上的一颗CPU。如果发生并发模式失效同时又有额外的CPU周期可用,可以设置-XX:ConcGCThreads=N增加后台线程数,默认情况下ConcGCThreads的值时依据ParallelGCThreads标志计算得到的。如下ConcGCThreads默认值为

ConcGCThreads=(3+ParallelGCThreads)/4

-XX:ParallelGCThreads=N (并行收集器的线程数)

上述计算使用整数计算方法。这以为着如果ParallelGCThreads的取值区间在1到4,gc默认线程就为1,如果默认cpu取值为5到8之间,gc默认线程就为2,以此类推。

CMS收集器的永久代调优

java7默认情况下CMS垃圾收集线程是不会处理永久代的垃圾,如果永久代空间用尽,CMS会发起一次Full GC来回收其中的垃圾对象。除此之外还可以设置一下参数

-XX:+CMSPermGenSweepingEnable(默认为false)开启后永久代的垃圾使用与老年代同样的方式进行垃圾收集。

-XX:CMSInitiatingPermOccupancyFraction=N(默认为80) 指定永久代占用空间达到%N时启动永久代垃圾回收线程

不过开启永久代垃圾收集只是整个流程的一步,为了真正释放不再被引用的类,还需要设置

-XX:+CMSClassUnloadingEnabled (默认时true)表示除了释放少量无效对象外还会释放类的元数据

java8中,CMS收集器默认就会收集元空间中不再载入的类。

该篇文章部分内容参考自《深入理解Java虚拟机》

声明:该文观点仅代表作者本人,入门客AI创业平台信息发布平台仅提供信息存储空间服务,如有疑问请联系rumenke@qq.com。