CMS垃圾回收器

news2025/1/11 11:18:40

概述

CMS(Concurrent Mark-Sweep)是以牺牲吞吐量为代价来获得最短回收停顿时间的垃圾回收器。对于要求服务器响应速度的应用上,这种垃圾回收器非常适合。在启动JVM参数加上-XX:+UseConcMarkSweepGC,这个参数表示对于老年代的回收采用CMS。CMS采用的基础算法是:标记—清除

CMS回收过程

CMS是基于“标记-清除”算法实现的,整个过程分为7个步骤

  1. 初始标记(STW)
  2. 并发标记
  3. 并发预清理
  4. 可中断并发预清理
  5. 重标记(STW)
  6. 并发清理
  7. 重置

初始标记

该阶段进行可达性分析,标记GC ROOT能直接关联到的对象。这里的GC root关联的对象包含虚拟机栈中引用的对象、类静态属性引用的对象、本地方法栈中JNI引用的对象。另外,CMS是老年代的垃圾收集器,被新生代引用的对象应该被标记为存活,所以这里还包含新生代对象。
例如:

public class Client {
    public static void main(String[] args) {
        Master master = new Master();
        Subject subject = Master.addSubject(new Subject());
        subject.publishEvent();
    }
}
 public class Master {
        public static Subject addSubject(Subject subject) {
            ObserverOne observer = new ObserverOne();
            subject.addObserver(observer);
            return subject;
        }
    }

上述代码中 ObserverOne对象就不是GC ROOT直接关联的对象,直接关联的对象只有Master

并发标记

该阶段进行GC ROOT TRACING,在整个过程中耗时最长,第一个阶段被暂停的线程(STW)重新开始运行。由前阶段标记过的对象出发,所有可到达的对象都在本阶段中标记。
该阶段使用三色遍历法遍历标记老年代的所有对象。
三色表示

  • 黑色对象:自己被标记,且引用对象已经处理完成。
  • 灰色对象:自己被标记,但引用对象未处理。
  • 白色对象:没有对它做标记。

标记过的意思就是认为这个对象是存活的,本次GC不回收这个对象。
CMS采用了线性遍历
引入一个bit数组,用它表示内存中每个位置的状态,每个bit位代表4字节的内存空间,所以它的大小是固定的。
然后遍历bitmap,找到被标记为存活的对象cur,将它压入栈中,然后开始遍历这个对象出发的所有对象。遍历栈的过程中,如果遇到地址比cur低的对象则标记并压如栈中,遇到地址比cur高则只标记不入栈。所有GC ROOT引用的对象已经在初始标记阶段标记成了存活对象,遍历过程遇到其中一个就开始利用栈遍历它及它之前的所有对象,这就保证了栈中最多有1个GC root直接引用的对象,有效控制了栈空间的大小。
因为该阶段并发执行的,在运行期间可能发生新生代的对象晋升到老年代、或者是直接在老年代分配对象、或者更新老年代对象的引用关系等等,对于这些对象,都是需要进行重新标记的,否则有些对象就会被遗漏,发生漏标的情况。为了提高重新标记的效率,本阶段会把这些发生变化的对象所在的Card标识为Dirty,这样后续就只需要扫描这些Dirty Card的对象,从而避免扫描整个老年代。
标记过程举例
在这里插入图片描述
步骤a:对a引用的对象bc,及b引用的对象e进行标记,根据当时的对象引用关系,abcegd是存活的。
步骤b:对象引用关系发生了改变,b不再引用c;新增引用d;g不再引用d
步骤c:完成了并发标记的过程,abceg被标记。第二个和第四个区域内的对象引用关系发生了改变,被记录了下来。这里面运用到了card table、mod union table数据结构和write barrier技术,详见下一节。
步骤d:实际上是重新标记后的结果,可以看到对象d在并发标记结束时未进行标记,但是它还在被对象b引用,不应该回收。这就依赖重新标记阶段对dirty card(对象引用关系发生变化的区域)的处理。
并发过程中变化的维护
JVM将内存分成一个个固定大小的card,然后有一个专门的数据结构(即这里的Card Table)维护每个Card Page的状态,一个字节对应一个Card。当一个Card上的对象的引用发生变化的时候,就将这个Card对应的Card Table上的状态置为dirty(实际上使用的是mod-union table)。之后在预清理和remark阶段会处理这些dirty card
在这里插入图片描述
card table是一个数组,数组中每个位置存的是一个字节(byte),每个比特位有不同的作用。CMS将老年代的空间分成大小为512bytes的块(一个CardPage大小为512bytes),card table中的每个元素对应着一个块。
对于新生代:它记录老年代到新生代的引用,Minor GC时不用遍历整个老年代
对于老年代:它记录并发标记开始引用发生变化的card,并发标记结束后需要处理这些card
由于新生代GC与老年代GC同时使用card table,所以会出现冲突的情况。新生代GC时,发现老年代的dirty card(card的一种状态)没有指向新生代的引用,会将这个card设置为clean(改变了老年代对象引用发生设置的状态),但这个card必须在remark阶段进行重新标记。所以增加了另一个数据结构mod union table解决此问题。
mod union table是一个bit位向量,一个bit表示一个card的状态。它由新生代垃圾收集器维护,新生代GC将card设置为clean之前,把mod union table设置为dirty。card table状态为dirty、或者mod union table标记为dirty、或者同时两种数据结构都标记为dirty的card表示并发标记阶段引用发生了变化,需要在后面的阶段进行处理。
write barrie写屏障类似于一个切面,用户线程写对象引用的时候就触发write barrier的逻辑,将对象所处的card设置为dirty。

并发预清理

通过参数 XX:-CMSPrecleaningEnabled关闭选择关闭该阶段,默认启用,主要做两件事情:

  1. 处理新生代已经发现的引用,比如在并发阶段,在Eden区中分配了一个A对象,A对象引用了一个老年代对象B(这个B之前没有被标记),在这个阶段就会标记对象B为活跃对象。
  2. 在并发标记阶段,如果老年代中有对象内部引用发生变化,会把所在的Card标记为Dirty,通过扫描这些Table,重新标记那些在并发标记阶段引用(Dirty Card)被更新的对象

可中断预清理

本阶段尽可能承担更多的并发预处理工作,从而减轻在Final Remark阶段的stop-the-world。在该阶段,主要循环的做两件事:

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

避免连续两次暂停导致总的暂停时间过长,其中运用了一些策略。预清理阶段结束之后,如果Eden空间大于CMSScheduleRemarkEdenSizeThreshold(默认2M),则进入可中断预清理阶段。当Eden空间达到CMSScheduleRemarkEdenPenetration(默认50%)时进入remark阶段。如果等待超过了CMSMaxAbortablePrecleanTime(默认5s)同样进入remark阶段。另外,还有个CMSMaxAbortablePrecleanLoops参数可以控制可中断预清理循环的次数,到达次数则退出预清理阶段进入remark,默认是0不限制次数。
-XX:CMSScheduleRemarkEdenSizeThreshold =n 设置可中断预清理触发条件,
-XX:CMSScheduleRemarkEdenPenetration=n 设置中断的条件
-XX:CMSMaxAbortablePrecleanTime=n,设置可中断预清理阶段最长持续时间,单位为s,默认值5s。
-XX:CMSMaxAbortablePrecleanLoops=n 设置该阶段的循环次数(默认0,表示不限制次数)

重新标记

暂停用户线程,从GC root(包含新生代对象)出发重新标记,并处理完所有dirty card。
因为预清理阶段也是并发执行的,并不一定是所有存活对象都会被标记,因为在并发标记的过程中对象及其引用关系还在不断变化中。因此,需要有一个stop-the-world的阶段来完成最后的标记工作,这就是重新标记阶段(CMS标记阶段的最后一个阶段)。主要目的是重新扫描之前并发处理阶段的所有残留更新对象
主要工作:

  • 遍历新生代对象,重新标记;(新生代会被分块,多线程扫描)
  • 根据GC Roots,重新标记;
  • 遍历老年代的Dirty Card,重新标记。

重新标记阶段需要遍历新生代对象,但新生代里大多都是垃圾,如果remark之前发生一次新生代GC,则会大大减小remark阶段需要遍历的对象数量。可以设置CMSScavengeBeforeRemark参数强制在remark之前执行一次新生代GC。但是新生代GC也是有停顿的,尤其在新生代对象很少的情况下触发YGC,最严重的是如果在可中断清理阶段已经发生了一次YGC,然后在该阶段又触发一次,会增加停顿时长。
-XX:+CMSScavengeBeforeRemark,强制hotspot虚拟机在cms remark阶段之前做一次minor gc,用于提高remark阶段的速度;

并发清理

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

并发重置

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

CMS的缺点

吞吐量低

并发意味着多线程抢占CPU资源,即GC线程与用户线程抢占CPU。这可能会造成用户线程执行效率下降。CMS默认的回收线程数是**(CPU个数+3)/4。**这个公式的意思是当CPU大于4个时,保证回收线程占用至少25%的CPU资源,这样用户线程占用75%的CPU,这是可以接受的。按照上面的公式,CMS会启动1个GC线程。相当于GC线程占用了50%的CPU资源,这就可能导致用户程序的执行速度忽然降低了50%,50%已经是很明显的降低了。

浮动垃圾

并发清理阶段用户线程还在运行,这段时间就可能产生新的垃圾,新的垃圾在此次GC无法清除,只能等到下次清理。

内存碎片

CMS使用标记清除算法,收集结束之后会产生大量内存碎片。当有大对象需要分配空间时,可能总的空间大小是足够的,但是没有连续的空间装下此对象。
CMS的解决方案是使用UseCMSCompactAtFullCollection参数(默认开启),在顶不住要进行Full GC时开启内存碎片整理。这个过程需要STW,碎片问题解决了,但停顿时间又变长了。
虚拟机还提供了另外一个参数CMSFullGCsBeforeCompaction,用于设置执行多少次不压缩的Full GC后,跟着来一次带压缩的(默认为0,每次进入Full GC时都进行碎片整理)。

Concurrent Mode Failure

CMS需要预留出空间提前开始GC,预留的空间供并发期间新对象的分配及新生代对象的晋升使用CMSInitiatingOccupancyFraction参数来设置老年代空间使用百分,达到百分比就进行垃圾回收。这个参数默认是92%,参数选择需要看具体的应用场景。
设置的太小会导致频繁的CMS GC,产生大量的停顿;设现在设置为99%,还剩1%的空间可以使用。在并发清理阶段,若用户线程需要使用的空间大于1%,就会产生Concurrent Mode Failure错误,意思就是说并发模式失败。这时,虚拟机就会启动备案:使用Serial Old收集器重新对老年代进行垃圾回收。如此一来,停顿时间变得更长。所以CMSInitiatingOccupancyFraction的设置要具体问题具体分析。
-XX:CMSInitiatingOccupancyFraction=70 和-XX:+UseCMSInitiatingOccupancyOnly这两个设置一般配合使用,一般用于降低CMS GC频率或者增加频率、减少GC时长
-XX:CMSInitiatingOccupancyFraction=70 是指设定CMS在对内存占用率达到70%的时候开始GC(因为CMS会有浮动垃圾,所以一般都较早启动GC);
-XX:+UseCMSInitiatingOccupancyOnly 只是用设定的回收阈值(上面指定的70%),如果不指定,JVM仅在第一次使用设定值,后续则自动调整.
-XX:+CMSScavengeBeforeRemark在CMS GC前启动一次Minor GC,目的在于减少老年代对新生代的引用,降低remark时的开销

触发时机

  1. 老年代可用内存小于新生代全部对象的大小,如果没开启空间担保参数,会直接触发Full GC,所以一般空间担保参数都会打开
  2. 老年代可用内存小于历次新生代GC后进入老年代的平均对象大小,此时会提前Full GC;
  3. 新生代Minor GC后的存活对象大于Survivor,那么就会进入老年代,此时老年代内存不足

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/25062.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SpringBoot SpringBoot 开发实用篇 5 整合第三方技术 5.13 j2cache 相关配置

SpringBoot 【黑马程序员SpringBoot2全套视频教程,springboot零基础到项目实战(spring boot2完整版)】 SpringBoot 开发实用篇 文章目录SpringBootSpringBoot 开发实用篇5 整合第三方技术5.13 j2cache 相关配置5.13.1 j2cache 相关配置5.13…

直播绿幕抠图的例子(绿幕抠图直播实例参考)

阿酷TONY / 2022-11-21 / 长沙 什么是绿幕抠图: 设定绿幕或绿布,做直播软件抠图,这时绿幕绿布就可以被实时的抠掉,绿色就变成透明了,只剩下绿幕外的人物,此时添加上直播的背景画质,就实现了绿…

Git——Git常用命令

目录 常用命令概览 1. 设置用户签名 2. 初始化本地库 2.1 初始化本地库 2.2 查看文件 2.3 查看隐藏文件 2.4 进入到下一个目录 3. 查看本地库状态 4.添加暂存区 4.1 删除文件 5. 提交本地库 5.1 将暂存区的文件提交到本地库 6. 查看版本信息的命令 7.修改文件 8. 历史版本…

【Python入门指北】服务器信息清洗

服务器信息清洗 文章目录服务器信息清洗一、 subprocess 执行本机命令二、 获取服务器的硬件基础信息1. 基础信息2. 厂家和产品信息3. CPU 信息3.1 查看物理CPU型号3.2 查看物理CPU颗数3.3 查看每颗物理 CPU 的核心数4. 内存信息练习内存处理参考代码一、 subprocess 执行本机命…

智云通CRM:如何提前识别哪些客户爱说“不”?

有人说,做业务是最好的锻炼意志力方法,因为做业务的人经常会被客户拒绝甚至会扫地出门。被拒绝时,业务员一定要擦亮眼睛,善于察言观色,洞察客户的心理活动。透过观察了解客户为什么说“不”,客户拒绝情况有…

聚观早报 | 推特临时培训员工应对世界杯;世界杯足球内置传感器

今日要闻:推特临时培训员工应对世界杯;京东靠降本增效实现转亏为盈;世界杯足球内置传感器;艾格重返迪士尼CEO职位;特斯拉明年或开启收购计划 推特临时培训员工应对世界杯 据消息, 2022年世界杯拉开帷幕,推特的使用量即将激增,其维…

陆地卫星(Landsat)计划:50多年的星球档案

陆地卫星计划:陆地卫星1号至陆地卫星9号 1967年,NASA(美国国家航空与航天局) 提出了“地球资源技术卫星”计划,从此开始了在理论上对地球资源技术卫星系列的可行性研究,于是,陆地卫星 (Landsat…

汽车安全气囊设计?Abaqus/Part特殊建模方法-附案例step-by-step教学

作者 | 邓怡超 Abaqus/Part基于特征的建模功能可以说非常齐全,基本能够满足一般的分析要求,更复杂的模型则可以通过与专业三维建模软件之间的接口来导入,今天要说的是部件的另外一种建模方法。 有一种类型的分析,部件自身的初始…

坚持自学软件测试,半年的辛苦没有白费,不过才拿到10k的offer

找软件测试的工作只用了一周的时间,因为自己的年纪已经25岁,所以在简历上包装了两年的工作经验,但是我学的技术水平自认为还可以,因为我当时自学时用的教程比较有深度。 之所以要转行,我相信做机械工作的朋友都明白&a…

神经网络-前向传播Forward propagation

前向传播Forward propagation 前向传播算法就是: 将上一层的输出作为下一层的输入,并计算下一层的输出,一直到运算到输出层为止 在正式介绍前向传播前,先简单介绍计算图(Computational Graph)的概念。 yw…

LiDAR 完整指南介绍:激光探测和测距

什么是激光探测和测距 (LiDAR)? LiDAR 的全称是 Light Detection and Ranging (激光探测及测距),LIDAR 是一种主动测量方式,主要由激光发射部分、接收部分组成、信号处理部分组成,从其名称可以发现 LIDAR 的两个主要基本功能是测…

关于我的家乡网页设计主题题材——梧州14页HTML+CSS网页

家乡旅游景点网页作业制作 网页代码运用了DIV盒子的使用方法,如盒子的嵌套、浮动、margin、border、background等属性的使用,外部大盒子设定居中,内部左中右布局,下方横向浮动排列,大学学习的前端知识点和布局方式都有…

iOS关于列表布局的几种实现方式小结

式 ,功能的要求是最多六行,动态展示。当时想到的方案是,抽象出一个cell,初始化六个标签,动态的控制显示和隐藏,这样功能上没有问题,就是代码有些冗余。请教了身边的美女同事,她那边的思路是用UI…

SpringBoot SpringBoot 开发实用篇 5 整合第三方技术 5.17 发送多部件邮件

SpringBoot 【黑马程序员SpringBoot2全套视频教程,springboot零基础到项目实战(spring boot2完整版)】 SpringBoot 开发实用篇 文章目录SpringBootSpringBoot 开发实用篇5 整合第三方技术5.17 发送多部件邮件5.17.1 发送多部件邮件5.17.2 添…

Python脚本之并发执行加密方法【一】

本文为博主原创,未经授权,严禁转载及使用。 本文链接:https://blog.csdn.net/zyooooxie/article/details/125650427 之前写过一篇 JMeter性能测试之参数加密【一】,现在把后面的补上。实际第一篇就写完了 JMeter压测遇到加密接口…

Java Class11

Java Class11 集合 概念 集合是用于存储对象的工具类容器,实现了常用的数据结构,提供了一系列公开的方法用于删除、修改、查找和遍历数据,降低了日常开发成本。 三种集合 Set set集合中元素是无序、不可重复的 List list集合中元素是从前到…

公众号免费搜题功能搭建

公众号免费搜题功能搭建 本平台优点: 多题库查题、独立后台、响应速度快、全网平台可查、功能最全! 1.想要给自己的公众号获得查题接口,只需要两步! 2.题库: 题库:题库后台(点击跳转&#xf…

基于C语言实现进度条 | 附源码

进度条的应用在软件中无处不在,拷贝一个文件需要一个进度条,加载一个文件也需要一个进度条,来标志完成与否。 那么 ,一个进度条有哪些元素呢: 一个不断向右生长的容器(直观的看出当前的进度) …

Spring的循环依赖问题

Spring中循环依赖场景有: 构造器的循环依赖(构造器注⼊) Field 属性的循环依赖(set注⼊) 各场景下循环依赖解决的情况 单例 bean 构造器参数循环依赖(⽆法解决) 构造器在Bean的生命周期中实例…

GitHub最新发布,阿里十年架构师手写版spring全家桶笔记全新开源

嗨咯,大家好! 没错,又是我,还跟前面一样,有好东西我才会出现。那是什么好东西呢?今天啊,给他分享阿里在Github最新发布的spring全家桶笔记第九版,这份笔记一共分三份:sp…