什么是CMS

  CMS全称 Concurrent Mark Sweep,是一款并发的、使用标记-清除算法的垃圾回收器,

使用场景

  GC过程短暂停,适合对时延要求较高的服务,用户线程不允许长时间的停顿。

实现机制

  1、不压缩老年代,而是使用空闲列表来管理回收空间。

  2、大部分标记清理工作与应用程序并发执行。

存在问题:

  1.对CPU资源敏感

    其实,面向并发设计的程序都对CPU资源比较敏感。在并发阶段,它虽然不会导致用户线程停顿,但是会因为占用了一部分线程(或者说CPU资源)而导致应用程序变慢,总吞吐量会降低。CMS默认启动的回收线程数是(CPU数量+3)/4,也就是当CPU在4个以上时,并发回收时垃圾收集线程不少于25%的CPU资源,随着CPU数量的增加而下降。

  2.无法处理浮动垃圾

    CMS并发清理时,用户程序的运行也会产生新的垃圾(一边打扫房间,一遍丢新的垃圾),但是这部分垃圾产生于标记过程之后,因此只好留在下次GC时清理,这种垃圾被称为浮动垃圾(Floating Garbage)。

  3.Concurrent Mode Failure

    由于CMS并发清理阶段,用户程序还在运行,也需要内存空间,因此CMS收集器不能像其他老年代收集器那样,等到老年代空间快满了再执行垃圾收集,而是要预留一部分内存给用户程序使用。CMS的做法是老年代空间占用率达到某个阈值时触发垃圾收集,有一个参数来控制触发百分比: -XX:CMSInitiatingOccupancyFraction=80 (这里配置的是80%)。

    如果预留的老年代空间不够应用程序的使用,就会出现Concurrent Mode Failure,此时会触发一次FullGC,使用Serial Old收集器重新对老年代进行垃圾回收,会发生stop-the-world,耗时相当感人(实际工作中遇到的大部分FGC估计都是这种情况)。Concurrent Mode Failure一般会伴随ParNew promotion failed,晋升担保失败。所谓晋升担保,就是为了应对新生代GC后存活对象过多,Survivor区无法容纳的情况,需要老年代有足够的空间容纳这些对象,如果老年代没有足够的空间,就会产生担保失败。

    为了避免Concurrent Mode Failure,可以采取的做法是:

      1.调大老年代空间;

      2.调低CMSInitiatingOccupancyFraction的值,但这样会造成更频繁的CMS GC;

      3.代码层面优化,控制对象创建频率。

  4.空间碎片

    这是「标记-清理」算法的通病,空间碎片过多时,将会给大对象分配带来很大麻烦,往往会出现老年代还有很大空间剩余,但是无法找到足够大的连续空间来分配当前对象,不得不提前触发一次Full GC。为了解决这个问题,CMS收集器提供了一个-XX:+UseCMSCompactAtFullCollection开关参数(默认就是开启的),用于在CMS收集器顶不住要进行FullGC时开启内存碎片的合并整理过程,内存整理的过程是无法并发的,空间碎片问题没有了,但停顿时间不得不变长。虚拟机设计者还提供了另外一个参数-XX:CMSFullGCsBeforeCom-paction,这个参数是用于设置执行多少次不压缩的Full GC后,跟着来一次带压缩的(默认值为0,表示每次进入FullGC时都进行碎片整理)。

CMS收集器的GC周期

  主要由7个阶段组成,其中有两个阶段会发生stop-the-world,其他阶段都是并发执行的。

Phase 1: Initial Mark(初始化标记)

初始化标记阶段,是CMS GC的第一个阶段,也是标记阶段的开始。主要工作是标记可直达的存活对象

主要标记过程

  • 从GC Roots遍历可直达的老年代对象
  • 遍历被新生代存活对象所引用的老年代对象

程序执行情况

  • 支持单线程或并行标记。
  • 发生stop-the-world,暂停所有应用线程。

(Marked obj:老年代绿色圆点表示被初始化标记的对象。)

在java中,可作为GC Roots的对象有:

  1.虚拟机栈(栈帧中的本地变量表)中引用的对象;

  2.方法区中的类静态属性引用的对象;

  3.方法区中常量引用的对象;

  4.本地方法栈中JNI(即一般说的Native方法)中引用的对象

Phase 2: Concurrent Mark(并发标记)

并发标记阶段,是CMS GC的第二个阶段。

在该阶段,GC线程和应用线程将并发执行。也就是说,在第一个阶段(Initial Mark)被暂停的应用线程将恢复运行。

并发标记阶段的主要工作是,通过遍历第一个阶段(Initial Mark)标记出来的存活对象,继续递归遍历老年代,并标记可直接或间接到达的所有老年代存活对象。


 

(Current obj:该对象的引用关系发生变化,对下一个对象的引用被删除)

由于在并发标记阶段,应用线程和GC线程是并发执行的,因此可能产生新的对象或对象关系发生变化,例如:

  • 新生代的对象晋升到老年代;
  • 直接在老年代分配对象;
  • 老年代对象的引用关系发生变更;

对于这些对象,需要重新标记以防止被遗漏。为了提高重新标记的效率,本阶段会把这些发生变化的对象所在的Card标识为Dirty,这样后续就只需要扫描这些Dirty Card的对象,从而避免扫描整个老年代。

Phase 3: Concurrent Preclean(并发预清理)

通过参数CMSPrecleaningEnabled选择关闭该阶段,默认启用。

在并发预清理阶段,将会重新扫描前一个阶段标记的Dirty对象(新生代晋升的对象新分配到老年代的对象以及在并发阶段被修改了的对象),并标记被Dirty对象直接或间接引用的对象,然后清除Card标识。

标记被Dirty对象直接或间接引用的对象:

清除Dirty对象的Card标识:

Phase 4: Concurrent Abortable Preclean(可中止的并发预清理)

本阶段尽可能承担更多的并发预处理工作,从而减轻在Final Remark阶段的stop-the-world。

该阶段发生的前提是,新生代Eden区的内存使用量大于参数CMSScheduleRemarkEdenSizeThreshold 默认是2M,如果新生代的对象太少,就没有必要执行该阶段,直接执行重新标记阶段。

在该阶段,主要循环的做两件事:

  • 处理 From 和 To 区的对象,标记可达的老年代对象;
  • 和上一个阶段一样,扫描处理Dirty Card中的对象。

具体执行多久,取决于许多因素,满足其中一个条件将会中止运行:

  • 可以设置最多循环的次数 CMSMaxAbortablePrecleanLoops,默认是0,意思没有循环次数的限制。
  • 如果执行这个逻辑的时间达到了阈值CMSMaxAbortablePrecleanTime,默认是5s,会退出循环。
  • 如果新生代Eden区的内存使用率达到了阈值CMSScheduleRemarkEdenPenetration,默认50%,会退出循环。(这个条件能够成立的前提是,在进行Precleaning时,Eden区的使用率小于十分之一)

如果在循环退出之前,发生了一次YGC,对于后面的Remark阶段来说,大大减轻了扫描年轻代的负担,但是发生YGC并非人为控制,所以只能祈祷这5s内可以来一次YGC。

Phase 5: Final Remark(重新标记)

预清理阶段也是并发执行的,并不一定是所有存活对象都会被标记,因为在并发标记的过程中对象及其引用关系还在不断变化中。

因此,需要有一个stop-the-world的阶段来完成最后的标记工作,这就是重新标记阶段(CMS标记阶段的最后一个阶段)。主要目的是重新扫描之前并发处理阶段的所有残留更新对象。

主要工作:

  • 遍历新生代对象,重新标记;(新生代会被分块,多线程扫描)
  • 根据GC Roots,重新标记;
  • 遍历老年代的Dirty Card,重新标记。这里的Dirty Card,大部分已经在Preclean阶段被处理过了。

CMS算法中提供了一个参数:CMSScavengeBeforeRemark,默认并没有开启,如果开启该参数,在执行该阶段之前,会强制触发一次YGC,可以减少新生代对象的遍历时间,回收的也更彻底一点。不过,这种参数有利有弊,利是降低了Remark阶段的停顿时间,弊的是在新生代对象很少的情况下也多了一次YGC,最可怜的是在AbortablePreclean阶段已经发生了一次YGC,然后在该阶段又傻傻的触发一次。所以利弊需要把握。

Phase 6: Concurrent Sweep(并发清理)

并发清理阶段,主要工作是清理所有未被标记的死亡对象,回收被占用的空间。

Phase 7: Concurrent Reset(并发重置)

并发重置阶段,将清理并恢复在CMS GC过程中的各种状态,重新初始化CMS相关数据结构,为下一个垃圾收集周期做好准备。

参考文档:

https://www.cnblogs.com/littleLord/p/5380624.html

https://www.iteye.com/blog/zhanjia-2435266

GC(二)CMS的更多相关文章

  1. JVM GC算法 CMS 详解(转)

    前言 CMS,全称Concurrent Low Pause Collector,是jdk1.4后期版本开始引入的新gc算法,在jdk5和jdk6中得到了进一步改进,它的主要适合场景是对响应时间的重要性 ...

  2. golang GC(二 定位)

    前面已经介绍过golang的GC算法.要是我们的程序在运行是因为GC导致行能下降,该如何定位呢?说实话,工作中由于对go的gc问题不重视,根本没考虑过这个问题,今天特意来补补课.

  3. 一次CMS GC问题排查过程(理解原理+读懂GC日志)

    这个是之前处理过的一个线上问题,处理过程断断续续,经历了两周多的时间,中间各种尝试,总结如下.这篇文章分三部分: 1.问题的场景和处理过程:2.GC的一些理论东西:3.看懂GC的日志 先说一下问题吧 ...

  4. [转]一次CMS GC问题排查过程(理解原理+读懂GC日志)

    这个是之前处理过的一个线上问题,处理过程断断续续,经历了两周多的时间,中间各种尝试,总结如下.这篇文章分三部分: 1.问题的场景和处理过程:2.GC的一些理论东西:3.看懂GC的日志 先说一下问题吧 ...

  5. Java中9种常见的CMS GC问题分析与解决

    1. 写在前面 | 本文主要针对 Hotspot VM 中"CMS + ParNew"组合的一些使用场景进行总结.重点通过部分源码对根因进行分析以及对排查方法进行总结,排查过程会省 ...

  6. 【转载】为什么不建议<=3G的情况下使用CMS GC

    之前曾经有讲过在heap size<=3G的情况下完全不要考虑CMS GC,在heap size>3G的情况下也优先选择ParallelOldGC,而不是CMS GC,只有在暂停时间无法接 ...

  7. 聊聊JVM(二)说说GC的一些常见概念

    转自CSDN 上一篇总结GC的基础算法,各种GC收集器的基本原理,还是比较粗粒度的概念.这篇会整理一些GC的常见概念,理解了这些概念,相信对GC有更加深入的理解 1. 什么时候会触发Minor GC? ...

  8. 理解CMS GC日志

    本文翻译自:https://blogs.oracle.com/poonam/entry/understanding_cms_gc_logs 准备工作 JVM的GC日志的主要参数包括如下几个:-XX:+ ...

  9. JVM 源码解读之 CMS 何时会进行 Full GC

    t点击上方"涤生的博客",关注我 转载请注明原创出处,谢谢!如果读完觉得有收获的话,欢迎点赞加关注. 前言 本文内容是基于 JDK 8 在文章 JVM 源码解读之 CMS GC 触 ...

  10. Java虚拟机5:Java垃圾回收(GC)机制详解

    哪些内存需要回收? 哪些内存需要回收是垃圾回收机制第一个要考虑的问题,所谓“要回收的垃圾”无非就是那些不可能再被任何途径使用的对象.那么如何找到这些对象? 1.引用计数法 这个算法的实现是,给对象中添 ...

随机推荐

  1. 基于Django的Rest Framework框架的视图组件

    本文目录 一 基本视图 二 mixin类和generice类编写视图 三 使用generics 下ListCreateAPIView,RetrieveUpdateDestroyAPIView 四 使用 ...

  2. C#中char[]与string之间的转换;byte[]与string之间的转化

    目录 1.char[]与string之间的转换 2.byte[]与string之间的转化 1.char[]与string之间的转换 //string 转换成 Char[] string str=&qu ...

  3. PHP中生成随机字符串,数字+大小写字母随机组合

    简单的生成随机字符串: /* * 生成随机字符串 * * $length 字符串长度 */ function random_str($length) { // 密码字符集,可任意添加你需要的字符 $c ...

  4. 转载-通过ApplicationContext 去获取所有的Bean

    Spring Boot - 获取所有的Bean信息 阅读目录 前言 通过ApplicationContext 去获取所有的Bean 前言 Spring Boot启动的时候需要加载许多Bean实现最小化 ...

  5. VSCode 开发插件 推荐

    VSCode 必装的 10 个高效开发插件  本文介绍了目前前端开发最受欢迎的开发工具 VSCode 必装的 10 个开发插件,用于大大提高软件开发的效率. VSCode 的基本使用可以参考我的原创视 ...

  6. oracle学习笔记(九) SQL常用函数说明以及使用

    SQL常用函数说明以及使用 以下补充以下常用的函数,更多的请看oracle函数API文档 to_char to_char(8.58,'9.99') to_char(8.50,'9.00') to_ch ...

  7. .net core项目启动时报_未处理Socket异常(以一种访问权限不允许的方式做了一个访问套接字的尝试。)

    解决方案:一般的原因就是程序的端口被占用了,关掉占用端口的程序即可正常使用.  查看启动地址和配置的webserver服务器的端口号是否被占用,可能占用的有:IIS启用项目,解决方案中其他启动项目配置 ...

  8. Z从壹开始前后端分离【 .NET Core2.2/3.0 +Vue2.0 】框架之九 || 依赖注入IoC学习 + AOP界面编程初探

    本文梯子 本文3.0版本文章 更新 代码已上传Github+Gitee,文末有地址 零.今天完成的绿色部分 一.依赖注入的理解和思考 二.常见的IoC框架有哪些 1.Autofac+原生 2.三种注入 ...

  9. Linux下载——下载文件的命令

    Linux下载——获取网络文件的命令 摘抄:本文主要学习了在Linux系统中如何下载文件的命令. wget命令 wget命令是一个用来下载文件的命令,可以在后台运行,在用户退出之后仍能继续下载,支持代 ...

  10. Java 包的使用

    Java 包 Java面向对象的核心的概念:类.接口.抽象类.对象:[主体] 包的定义: 指的是一个程序的目录,在最早的时候,如果要开发一个程序,只需要定义一个Java文件,而后在这个文件中编写所需要 ...