`
mc90716
  • 浏览: 10320 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

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日志:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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下该参数才有效

---------------------------------------------------------------

欢迎关注我的微信公众号:yunxi-talk,分享Java干货,进阶Java程序员必备。

分享到:
评论

相关推荐

    CMS垃圾收集器1

    CMS垃圾收集器是一种Java虚拟机(JVM)的老年代垃圾收集器,它的主要目标是减少垃圾收集时的停顿时间,以提供更优秀的响应速度,适合于需要低延迟且CPU资源充足的服务器应用。CMS基于“标记-清除”算法,这意味着它...

    漫谈Java垃圾收集器.pdf

    * 降低暂停时间:CMS垃圾收集器可以在不停止应用程序的情况下进行垃圾收集。 * 提高系统性能:CMS垃圾收集器可以提高系统的整体性能。 Java垃圾收集器的其他知识点包括: * GC Roots:垃圾收集器的根对象,包括...

    7种JVM垃圾收集器特点-优劣势-及使用场景.pdf

    CMS垃圾收集器可以使用多个线程来进行垃圾收集工作,并具有较高的垃圾收集性能和较低的停顿时间。 7. G1垃圾收集器 G1垃圾收集器是Java 7中引入的一种低停顿时间垃圾收集器,适用于具有大内存和高性能要求的应用...

    稳了!我准备了1个晚上的CMS垃圾收集器(csdn)————程序.pdf

    CMS(Concurrent Mark Sweep)垃圾收集器是一种Java虚拟机(JVM)中的垃圾回收机制,它的主要目的是为了减少老年代垃圾回收(Full GC)时的停顿时间,从而提高应用程序的响应速度。CMS使用并发标记清除算法,允许在...

    用了很多年的 CMS 垃圾收集器,终于换成了 G1,真香!!(csdn)————程序.pdf

    《CMS到G1的转变:理解G1垃圾收集器的工作原理》 CMS(Concurrent Mark Sweep)垃圾收集器曾是Java HotSpot VM的主流选择,然而随着内存规模的扩大和对软实时性能需求的增长,G1(Garbage-First)垃圾收集器逐渐...

    深入理解G垃圾收集器.docx

    G1垃圾收集器是Java 7引入的一个重要特性,自那时起,它在JDK 7及更高版本中可用,并逐渐成为替代CMS垃圾收集器的优选方案。G1的主要创新在于其将堆空间划分为多个独立的区域(Region),这种设计使得G1能够更智能地...

    cms-java-源码

    这个源码包"cms-java-源码.zip"显然是针对CMS垃圾收集器的Java实现进行的开源分享,旨在帮助开发者深入理解CMS的工作原理及其在Java中的实现细节。 CMS垃圾收集器的主要特点是并发性和低停顿时间。它主要分为四个...

    JDK11-hotspot-virtual-machine-garbage-collection-tuning-guide

    * Concurrent Mark-and-Sweep (CMS) 垃圾收集器:使用多线程进行垃圾收集,适合需要低 pause 时间的应用程序。 * G1 垃圾收集器:使用Region-based垃圾收集,适合需要高吞吐量和低 pause 时间的应用程序。 垃圾收集...

    JDK19-hotspot-virtual-machine-garbage-collection-tuning-guide

    3. Concurrent Mark-and-Sweep(CMS)垃圾收集器:CMS垃圾收集器使用标记-清除算法,适合大型应用程序。 4. Garbage-First(G1)垃圾收集器:G1垃圾收集器是Java 7中引入的新型垃圾收集器,使用增量式标记-清除算法...

    2-6垃圾收集器ParNew&CMS底层三色标记.mp4

    2-6垃圾收集器ParNew&CMS底层三色标记.mp4

    Java理解CMS收集器.pdf

    CMS(Concurrent Mark Sweep)收集器是Java虚拟机中的一种垃圾收集器,主要针对老年代的内存回收,其特点是并发和低停顿。CMS收集器的主要目标是在尽可能短的时间内完成垃圾收集,减少应用程序的暂停时间,提高用户...

    JVM 45 道面试题及答案.docx

    Java 虚拟机(JVM)面试题及答案 ...JVM 中有多种垃圾收集器类型,包括 Serial 垃圾收集器、ParNew 垃圾收集器、Parallel Scavenge 垃圾收集器、CMS 垃圾收集器等。不同的垃圾收集器类型适合不同的应用场景。

    Tomcat中Java垃圾收集调优分享.pdf

    `-XX:CMSInitiatingOccupancyFraction`是CMS垃圾收集器的参数,它定义了何时开始执行CMS收集,以避免因年老代空间不足导致的Full GC。例如,将其设置为70意味着当年老代占用达到70%时开始CMS,从而减少Full GC的频率...

    04-VIP-JVM垃圾收集器详解1

    在Java世界中,JVM垃圾收集器是内存管理的重要组成部分,负责自动回收不再使用的对象,以释放内存空间。垃圾收集器的选取并非一成不变,而是需要根据具体应用的需求和环境来定制。本文将详细解析几种常见的JVM垃圾...

    钻研JAVA虚拟机 全面掌握JVM JAVA虚拟机深入浅出实战课程 视频附带课程代码

    ├─(12) 0812_【了解】CMS垃圾收集器.mp4 ├─(13) 0813_【掌握】G1垃圾收集器.mp4 ├─(14) 0814_【理解】ZGC垃圾收集器.mp4 ├─(15) 0815_【理解】jstat监控工具.mp4 ├─(16) 0816_【理解】jmap监控工具.mp4 ├...

    Java垃圾收集器参考.pdf

    Java垃圾收集器是Java语言的核心特性之一,它自动化地处理内存管理,使得程序员无需手动回收内存,从而减少了潜在的内存泄漏问题。Java虚拟机(JVM)中的垃圾收集器通过一个低优先级的线程——垃圾收集器线程来监控...

    JVM垃圾收集器全面详解

    ParNew是Serial GC的多线程版本,主要在新生代进行工作,与CMS(Concurrent Mark Sweep)垃圾收集器配合使用,适合多CPU环境。它可以减少暂停时间,但可能会增加CPU使用率。 3. **Parallel GC** Parallel GC也...

    JVM初探- 内存分配、GC原理与垃圾收集器

    最后,JVM中提供了多种垃圾收集器,如Serial、Parallel、CMS、G1等,每种收集器都有其适用的场景和特点。例如,Serial收集器是单线程的,适用于小内存环境;而CMS(Concurrent Mark Sweep)收集器是追求低停顿时间的...

Global site tag (gtag.js) - Google Analytics