CMS垃圾收集器
介绍
CMS垃圾回收器的全称是Concurrent Mark-Sweep Collector,从名字上可以看出两点,一个是使用的是并发收集,第二个是使用的收集算法是Mark-Sweep。从而也可以推测出该收集器的特点是低延迟并且会有浮动垃圾的问题。下面详细介绍一下这个收集器的特点。
CMS收集器
CMS收集器是为了低延迟而生,通过尽可能的并行执行垃圾回收的几个阶段来把延迟控制到最低。CMS收集器是老年代的垃圾收集器,一般情况下会有ParNew来配合执行(默认情况下也是ParNew),ParNew也是使用并行的算法来执行年轻代的回收。当然除此之外,你还可以选择使用Serial收集器来收集年轻代,不过一般很少这样使用。通过咱们说的CMS收集器是指广义上的CMS收集器,包含以下几个:ParNew(Young)GC + CMS(Old)GC + Serial GC 算法(应对核心的CMS GC某些时候的不赶趟,开销很大)。本文重点介绍一些CMS在Old区域的回收。
触发条件
CMS垃圾收集器的触发条件有以下几个:
1、如果没有设置-XX:+UseCMSInitiatingOccupancyOnly,虚拟机会根据收集的数据决定是否触发(建议带上这个参数)。
2、老年代使用率达到阈值 CMSInitiatingOccupancyFraction,默认92%,前提是配置了第一个参数。
3、永久代的使用率达到阈值 CMSInitiatingPermOccupancyFraction,默认92%,前提是开启 CMSClassUnloadingEnabled并且配置了第一个参数。
4、新生代的晋升担保失败。
CMS的收集阶段
CMS收集器在收集老年代的时候分为以下几个阶段:初始标记、并发标记、预清理、可中断预清理、最终标记、并发清除、并发重置。每个阶段的运行过程如下:
下面分别从这几个阶段来介绍CMS收集器。
初始标记
初始标记阶段主要做两件事:一是遍历GCRoot可直达的老年代对象;二是遍历新生代直达的老年代对象。这里的直达是指直接关联到GCRoot的一级对象。初始标记阶段是完全STW的,引用程序会暂停。通过-XX:+CMSParallelInitialMarkEnabled参数可以开启该阶段的并行标记,使用多个线程进行标记,减少暂停时间。
哪些对象可以作为GCRoot:
1、所有Java线程当前栈帧引用的,也就是正在被调用的方法的引用类型的参数、局部变量以及临时值。
2、所有的静态数据结构引用的对象
3、String常量池里的引用
4、运行时常量池里引用的类型
并发标记
并发标记阶段是与应用程序一起执行的,这个阶段主要做两件事:
1、对初始标记中标记的存活对象进行trace,标记这些对象为可达对象,例如A->B,A在初始标记被识别,而B就是在并发标记阶段被识别。
2、将在并发阶段新生代晋升到老年代的对象、直接在老年代分配的对象以及老年代引用关系发生变化的对象所在的card标记为dirty,避免在重新标记阶段扫描整个老年代。
因为并发标记阶段与引用程序一起执行,因此会出现之前A->B->C变成A->C的情况,这种情况下C对象时无法在并发标记阶段被标记的。在标记阶段会使用三色标记算法。
三色标记法把 GC 中的对象划分成三种情况:
白色:还没有搜索过的对象(白色对象会被当成垃圾对象)
灰色:正在搜索的对象
黑色:搜索完成的对象(不会当成垃圾对象,不会被 GC)
假设刚开始的对象图如下:
在并发标记阶段,B对象引用C对象变为A对象引用C对象,如下
这时候再扫描的时候就会变成如下的图
此时C对象变成了白色的,但是显然是不正确的,于是就有两种方式来处理这种情况:
1、在对象引用发生变化之前记录对象引用关系
2、在对象引用发生变化之后记录对象引用关系
这两种思路正好对应了CMS和G1的两种处理方式。在CMS采用的是增量更新(Incremental update),只要在写屏障(write barrier)里发现要有一个白对象的引用被赋值到一个黑对象 的字段里,那就把这个白对象变成灰色的。即插入的时候记录下来。在G1中,使用的是STAB(snapshot-at-the-beginning)的方式,删除的时候记录所有的对象
预清理
通过参数CMSPrecleaningEnabled选择关闭该阶段,默认启用,主要做两件事情
1、并发标记阶段在Eden分配了对象A,并且A引用了老年代对象B,那么这个阶段标记B为活跃对象
2、扫描并发标记阶段的Dirty Card,重新标记那些在并发标记阶段引用被更新的对象
可中断预清理
该阶段存在的目的是减轻重新标记的工作量,减少暂停时间,主要做两件事情:
1、扫描处理DirtyCard中的对象
2、处理新生代引用到的老年代的对象
该阶段退出的条件有三个:
1、CMSMaxAbortablePrecleanTime参数控制的5秒退出
2、Eden区达到CMSScheduleRemarkEdenPenetration参数配置的值(默认50%)
3、CMSMaxAbortablePrecleanLoops控制的扫描次数(默认是0,不退出)
该阶段是希望能发生一次Young GC,这样就可以减少Eden区对象的数量,降低重新标记的工作量,因为重新标记会扫描整个Eden区的
最终标记
最终标记又叫重新标记,该阶段也是STW的,主要会遍历三个地方:
1、遍历Eden区,重新标记
2、遍历DirtyCard,重新标记
3、遍历GC Root,重新标记
由于该阶段遍历的区域很多,因此有可能会耗时比较长,并且该阶段是完全的STW的。
通过CMSScavengeBeforeRemark参数可以强制在重新标记阶段之前强制进行一次YoungGC,通过设置CMSParallelRemarkEnabled参数可以开启并行的Remark,加快remark的速度。
并发清理
移除那些不用的对象,回收他们占用的空间并且为将来使用。该阶段有可能产生浮动垃圾,可以通过参数UseCMSCompactAtFullCollection和CMSFullGCsBeforeCompaction来控制压缩次数。
并发重置
该阶段是最后一个阶段,重置CMS的数据结构。
CMS日志说明
这是一段完整的CMS日志:
- 2018-04-28T01:18:00.485+0800: 30991.893: [GC (CMS Initial Mark) [1 CMS-initial-mark: 845243K(1228800K)] 893692K(2611200K), 0.0195530 secs] [Times: use
- r=0.04 sys=0.00, real=0.02 secs]
- 2018-04-28T01:18:00.505+0800: 30991.913: [CMS-concurrent-mark-start]
- 2018-04-28T01:18:00.944+0800: 30992.353: [CMS-concurrent-mark: 0.429/0.439 secs] [Times: user=0.94 sys=0.00, real=0.44 secs]
- 2018-04-28T01:18:00.944+0800: 30992.353: [CMS-concurrent-preclean-start]
- 2018-04-28T01:18:00.953+0800: 30992.362: [CMS-concurrent-preclean: 0.009/0.009 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]
- 2018-04-28T01:18:00.953+0800: 30992.362: [CMS-concurrent-abortable-preclean-start]
- CMS: abort preclean due to time 2018-04-28T01:18:06.050+0800: 30997.459: [CMS-concurrent-abortable-preclean: 5.089/5.097 secs] [Times: user=5.92 sys=
- 0.17, real=5.09 secs]
- 2018-04-28T01:18:06.053+0800: 30997.462: [GC (CMS Final Remark) [YG occupancy: 222099 K (1382400 K)]30997.462: [Rescan (parallel) , 0.0285059 secs]309
- 97.491: [weak refs processing, 0.0334976 secs]30997.524: [class unloading, 0.0806913 secs]30997.605: [scrub symbol table, 0.0359491 secs]30997.641: [s
- crub string table, 0.0035671 secs][1 CMS-remark: 845243K(1228800K)] 1067342K(2611200K), 0.1878564 secs] [Times: user=0.38 sys=0.00, real=0.18 secs]
- 2018-04-28T01:18:06.242+0800: 30997.650: [CMS-concurrent-sweep-start]
- 2018-04-28T01:18:06.659+0800: 30998.068: [CMS-concurrent-sweep: 0.417/0.417 secs] [Times: user=0.55 sys=0.00, real=0.42 secs]
- 2018-04-28T01:18:06.660+0800: 30998.069: [CMS-concurrent-reset-start]
- 2018-04-28T01:18:06.663+0800: 30998.072: [CMS-concurrent-reset: 0.003/0.003 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
这段日志展示了CMS回收时的各个阶段,在CMS Initial Mark阶段,老年代容量是1228800K,目前使用了845243K,该阶段耗时0.0195530秒,这段时间是STW的;接下来进行concurrent-mark阶段,耗时0.429秒;preClean阶段耗时0.009秒;再往下是abortable-preclean阶段,这个阶段耗时5秒,结束的原因是最长配置的该阶段运行时间是5s,这个阶段并没有发生YoungGC;接下来是第二个STW,Final Remark阶段,222099 K (1382400 K)分别是年轻代的占用和总空间情况,该阶段还会进行弱引用的处理、class卸载以及符号表的扫描,845243K(1228800K)是老年代的使用量和总空间,这个阶段总共耗时0.1878564秒;标记完成之后就开始concurrent-sweep阶段进行并发清除,然后运行concurrent-reset重置CMS数据结构。
CMS失败处理
在运行CMS收集器的时候,可能会出现两种类型的失败
并发模式失败日志(concurrent mode failure)
当老年代无法容纳新生代GC晋升的对象时发生并发模式失败,并发模式失败意味着CMS退化成完全STW的Full GC,也就是Serial GC。下面的图片揭示了在发生并发模式失败时的日志:
针对这种情况,有两个方面需要考虑:
1、给后台线程更多的运行机会。也就是说更早的启动并发收集周期。CMS收集器在老年代使用占到60%的时候启动比占到70%才启动,显然前者完成垃圾回收的几率更大。为了实现这种配置,可以同时设置以下两个参数:-XX:CMSInitiatingOccupancyFraction=N和-XX:+UseCMSInitiatingOccupancyOnly。如果同时设置了这两个参数就可以让CMS只根据老年代的使用比例来决定是否启动CMS垃圾收集。
2、更多的线程来运行CMS。之所以出现并发模式失败,是因为CMS的速度跑不赢对象晋升到老年代的速度了。所以可以通过给CMS更多的线程来加快CMS的速度。可以通过-XX:ConGCThreads=N来设置后台线程的数量。默认情况下线程数ConcGCThreads=(3+ParallelGCThreads)/4,是根据ParallelGCThreads来计算的,ParallelGCThreads的值可以通过-XX:ParallelGCThreads参数来设置。并不是说设置越多的线程来运行CMS越好,因为CMS在运行的时候会完整的占用一颗CPU,所以在CPU比较紧张的情况下,这个值还是要谨慎设置的。
晋升失败(promoration failure)
老年代有足够的空间,但是由于碎片化严重,无法容纳新生代中晋升的对象,发生晋升失败。下面的图片揭示了在发生晋升失败时的日志:
晋升失败的原因是碎片化严重,所以这个问题的解决方案就是如何减少碎片化的问题。CMS提供了两个参数来对碎片进行整理和压缩。-XX:+UseCMSCompactAtFullCollection这个设置的作用是在进行FullGC的时候对碎片进行整理和压缩。-XX:CMSFullGCsBeforeCompaction=*这个参数是设置在进行多少次FullGC的时候对老年代的内存进行一次碎片整理压缩。通过设置这两个参数可以有效的对碎片问题进行优化。同样需要注意的是对碎片进行整理压缩是一个比较耗时的操作,所以也需要谨慎设置。
CMS的一些参数说明
结合上面已经讲过的参数配置,下面给出CMS的一些参数说明:
-XX:+UseConcMarkSweepGC 激活CMS收集器
-XX:ConcGCThreads 设置CMS线程的数量
-XX:+UseCMSInitiatingOccupancyOnly 只根据老年代使用比例来决定是否进行CMS
-XX:CMSInitiatingOccupancyFraction 设置触发CMS老年代回收的内存使用率占比
-XX:+CMSParallelRemarkEnabled 并行运行最终标记阶段,加快最终标记的速度
-XX:+UseCMSCompactAtFullCollection 每次触发CMS Full GC的时候都整理一次碎片
-XX:CMSFullGCsBeforeCompaction=* 经过几次CMS Full GC的时候整理一次碎片
-XX:+CMSClassUnloadingEnabled 让CMS可以收集永久带,默认不会收集
-XX:+CMSScavengeBeforeRemark 最终标记之前强制进行一个Minor GC
-XX:+ExplicitGCInvokesConcurrent 当调用System.gc()的时候,执行并行gc,只有在CMS或者G1下该参数才有效
CMS垃圾收集器的更多相关文章
- G1垃圾收集器和CMS垃圾收集器 (http://mm.fancymore.com/reading/G1-CMS%E5%9E%83%E5%9C%BE%E7%AE%97%E6%B3%95.html#toc_8)
参考来源 JVM 体系架构 堆/栈的内存分配 静态和非静态方法的内存分配 CMS 回收算法 应用场景 CMS 垃圾收集阶段划分(Collection Phases) CMS什么时候启动 CMS缺点 G ...
- 实例透彻分析CMS垃圾收集器执行过程
CMS收集器收集步骤: 在上一次[https://www.cnblogs.com/webor2006/p/11055468.html]中已经对CMS的垃圾收集器有了一定的理论上的了解,其中提到了CMS ...
- CMS垃圾收集器——重新标记和浮动垃圾的思考
<深入理解java虚拟机 第二版 JVM高级特性与最佳实践>里面提到 CMS 垃圾收集器. CMS 垃圾收集器的垃圾回收分4个步骤: 初始标记(initial mark) 有 STW 并发 ...
- 稳了!我准备了1个晚上的CMS垃圾收集器
面试官:今天还是来聊聊CMS垃圾收集器呗? 候选者:嗯啊... 候选者:如果用Seria和Parallel系列的垃圾收集器:在垃圾回收的时,用户线程都会完全停止,直至垃圾回收结束! 候选者:CMS的全 ...
- CMS垃圾收集器与G1收集器
1.CMS收集器 CMS收集器是一种以获取最短回收停顿时间为目标的收集器.基于“标记-清除”算法实现,它的运作过程如下: 1)初始标记 2)并发标记 3)重新标记 4)并发清除 初始标记.从新标记这两 ...
- CMS垃圾收集器深入详解
上一次[https://www.cnblogs.com/webor2006/p/11048407.html]对安全点和安全区进行了理论化的了解,接下来继续对CMS进行其它理论的了解,还是纯理论!!坚持 ...
- [转]使用CMS垃圾收集器产生的问题和解决方案
在之前的一篇文章<CMS vs. Parallel GC>里通过实验的方式对比了并行和并发GC的优缺点,在文章结尾提到,CMS并行GC是大多数应用的最佳选择,然而, CMS并不是完美的,在 ...
- 深入理解java虚拟机【Java虚拟机垃圾收集器】
Java堆内存被划分为新生代和年老代两部分,新生代主要使用复制和标记-清除垃圾回收算法,年老代主要使用标记-整理垃圾回收算法,因此java虚拟中针对新生代和年老代分别提供了多种不同的垃圾收集器,JDK ...
- (转)《深入理解java虚拟机》学习笔记4——Java虚拟机垃圾收集器
Java堆内存被划分为新生代和年老代两部分,新生代主要使用复制和标记-清除垃圾回收算法,年老代主要使用标记-整理垃圾回收算法,因此java虚拟中针对新生代和年老代分别提供了多种不同的垃圾收集器,JDK ...
随机推荐
- POJ- 1094 Sorting It All Out---拓扑排序是否唯一的判断
题目链接: https://vjudge.net/problem/POJ-1094 题目大意: 该题题意明确,就是给定一组字母的大小关系判断他们是否能组成唯一的拓扑序列.是典型的拓扑排序,但输出格式上 ...
- Tab标签js切换高效率写法
原来的传统写法: function tabit(id,cid) { for(var i=0; i<10; i++){ gi(["tab"+i]).className = &q ...
- [LeetCode] Subarray Product Less Than K 子数组乘积小于K
Your are given an array of positive integers nums. Count and print the number of (contiguous) subarr ...
- 关于redis主从|哨兵|集群模式
关于redis主从.哨兵.集群的介绍网上很多,这里就不赘述了. 一.主从 通过持久化功能,Redis保证了即使在服务器重启的情况下也不会损失(或少量损失)数据,因为持久化会把内存中数据保存到硬盘上,重 ...
- hibernate--hibernate.cfg.xml常用配置详解
在hibernate中最重要的两个配置文件就是hibernate.cfg.xml和xxx.hbm.xml文件,前者是一些配置信息,后者是配置表和对象的映射关系,可以通过注解的方式来取代. 本篇文章主要 ...
- python学习记录2
一.两个模块(sys和os) #!/usr/bin/env python # _*_ coding: UTF-8 _*_ # Author:taoke import sys print(sys.pat ...
- hdu 4352 数位dp + 状态压缩
XHXJ's LIS Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total ...
- ●BZOJ 1797 [Ahoi2009]Mincut 最小割
题链: http://www.lydsy.com/JudgeOnline/problem.php?id=1797 题解: 详细的讲解去看http://hzwer.com/3217.html首先跑一个最 ...
- poj 3693 后缀数组 重复次数最多的连续重复子串
Maximum repetition substring Time Limit: 1000MS Memory Limit: 65536K Total Submissions: 8669 Acc ...
- 习题9-4 uva 1630
题意: 给你一串数字,要求你对其进行折叠使其长度最短. 折叠情况:全是一个字母 & 重复的字符串 AAAAAAAAAABABABCCD --> 9(A)3(AB)CCD NEE ...