大概了解一下CMS收集器

1. 标记清除三兄贵

标记清除法是最常用的垃圾回收方法,其整体上可以分为 “标记阶段” 和 “清除阶段” 两个阶段。

  • 标记阶段常用的标记方法为三色标记法,可以参照这篇文章:自己给自己引流
  • 清除阶段则根据策略不同,一般分为直接清除、复制、压缩(或整理)三种。

1.1 基础标记清除算法

过程:

  1. 遍历标记垃圾内存
  2. 清除被标记的垃圾内存

优点:

  • 没有额外的操作过程,速度较快

缺点:

  • 会产生大量内存碎片,不利于内存复用

在这里插入图片描述

1.2 标记复制算法

将内存分为From和To两部分,创建对象时,只从From部分分配内存,在垃圾回收时,将From中的存活对象复制到To的部分,清空From,并将From和To交换。

过程:

  1. 遍历From区域中的对象进行标记
  2. 将From中所有未被标记成垃圾内存的对象复制到To区域,并整理
  3. 清空From区域中所有内容
  4. From区域变成新的To区域,To区域变成新的From区域

在这里插入图片描述
优点:

  • 不会产生内存碎片

缺点:

  • 需要移动对象,当存活对象较多或者移动大对象时速度较慢
  • To区域作为复制目标的备用内存始终空着,存在内存浪费

1.3 标记压缩(标记整理)算法

过程:

  1. 变量标记垃圾内存
  2. 清除被标记的垃圾内存
  3. 移动存活的对象,从内存区域的一侧依次排列

优点:

  • 不会产生内存碎片
  • 不需要备用内存区域,避免内存浪费

缺点:

  • 依然存在因为移动对象带来的效率问题

在这里插入图片描述

2. 常见收集器

在这里插入图片描述

盗图:
在这里插入图片描述

这里依然是以JAVA环境为背景来介绍。常见的垃圾收集器及其作用代际目标如上图,对各个收集器的简单介绍可以参照这篇文章:
来自某乎的链接

3. CMS(Concurrent Mark Sweep)

3.1 核心过程

CMS是针对旧生代对象(永久代可选)进行清理的GC过程,除手动调用外,主要通过周期性查询是否满足触发条件来决定是否执行。最主要的触发条件包括以下两类:

  • 旧生代(或永久代)占用量达到阈值
  • 初生代晋升担保失败——旧生代剩余容量不足以满足初生代对象晋升到旧生代

其核心过程如下(省略了部分):

  • 初始标记(STW)
  • 并发标记
  • 重标记(STW)
  • 并发清除

初始标记阶段

  • 该阶段需要STW。
  • 遍历ROOT,将所有ROOT直接可达的对象标灰添加至灰栈(To-Scanned-Set,通过Stack实现)。
  • 本阶段虽然是STW,但因为只需要查找ROOT直接可达的对象,扫描对象较少,因此速度较快。

并发标记阶段

  • 该阶段不需要STW,可与业务线程并发执行。
  • GC线程从灰栈出发,使用三色标记法递归遍历标记所有可达对象。
  • 由于当前阶段是与业务线程并发执行的,因此会存在标记过程中对象引用状态被业务线程改变造成出现错标。CMS通过增量更新的方式解决这个问题,即在黑色对象直接引用白色对象后,对白色对象设置脏卡,留待下一阶段处理。可见CMS的增量更新方法为插入写屏障机制,且为写后屏障。
  • 增量更新——写后屏障——插入写屏障——强三色不变原则
  • 本阶段扫描对象巨大,时间最久,但由于是并发执行,因此并不阻断程序。

重标记阶段

  • 该阶段需要STW。
  • 以脏卡对象、栈上对象、初生代对象为出发点,重新进行递归扫描 扫描过程会跳过并发标记阶段已经处理过的对象
  • 本阶段主要目的是为了处理并发标记阶段中被业务线程改变的引用,由于本阶段扫描过程中业务线程被停止运行,因此不会产生新的错标问题。
  • 本阶段扫描数量较多,用时较长,仅次于并发标记阶段,且由于是STW的,对程序阻断明显,也是GC过程中卡顿的最主要来源。
  • 为降低该阶段处理对象的数量,实际会在并发标记极端结束和重标记阶段开始之前插入两个预处理阶段。

并发清除阶段

  • 该阶段不需要STW,可与业务线程并发执行。
  • 此时标记阶段已经完全结束,GC线程清除此前被标记的垃圾内存,同时业务线程可并发执行。
  • 由于业务线程与清理线程在同时工作,为避免出现程序错误,CMS的清理采用的是基础的标记清理,不需要移动对象,但会产生内存碎片。
  • 此阶段由于要清理大量垃圾内存,用时也较长,但因为与业务线程并发执行,因此也不会阻断程序。

3.2 内存碎片问题

上文提到,CMS是清理旧生代的Old GC,且会产生内存碎片。对于产生的内存碎片,CMS本身并不做处理,而是在JVM进行Full GC的时候通过标记压缩的方式进行处理。
CMS提供了两个参数可用于配置对内存碎片的清理频率:
-XX:+UseCMSCompactAtFullCollection Full GC时会进行内存压缩整理
-XX:CMSFullGCsBeforeCompaction 每执行指定次数的普通Full GC后,执行一次进行内存压缩整理的Full GC

3.3 CPU占用

在这里插入图片描述
从网上盗了一张图,比较清晰地表示出了CMS的过程。从这张图里可以看到,CMS通过将费时最长的标记和清理阶段与用户线程并发来达到降低阻断的效果,但是也带来另一个问题,就是并发过程会分走CPU核心,具体GC占用的线程数好像是(CPU核心数 + 3)/4,也又说(ParallelGCThreads+3) / 4 的,总之会在并发的过程中分走相当一部分CPU资源,导致程序变慢。

最后再贴一段

CMS收集器对CPU资源非常敏感。其实,面向并发设计的程序都对CPU资源比较敏感。
在并发阶段,它虽然不会导致用户线程停顿,但是会因为占用了一部分线程(或者说CPU资源)而导致应用程序变慢,总吞吐量会降低。
CMS默认启动的回收线程数是(CPU数量+3)/4,也就是当CPU在4个以上时,并发回收时垃圾收集线程不少于25%的CPU资源,并且随着CPU数量的增加而下降。但是当CPU不足4个(譬如2个)时,CMS对用户程序的影响就可能变得很大,如果本来CPU负载就比较大,还分出一半的运算能力去执行收集器线程,就可能导致用户程序的执行速度忽然降低了50%,其实也让人无法接受。
为了应付这种情况,虚拟机提供了一种称为“增量式并发收集器”(Incremental Concurrent Mark Sweep/i-CMS)的CMS收集器变种,所做的事情和单CPU年代PC机操作系统使用抢占式来模拟多任务机制的思想一样,就是在并发标记、清理的时候让GC线程、用户线程交替运行,尽量减少GC线程的独占资源的时间,这样整个垃圾收集的过程会更长,但对用户程序的影响就会显得少一些,也就是速度下降没有那么明显。
实践证明,增量式的CMS收集器效果很一般,在目前版本中,i-CMS已经被声明为"deprecated",即不再提倡用户使用。
引用自《深入理解java虚拟机》 周志明著

本来想CMS和G1一起简单过一遍,没想到还挺长,G1放到下一篇再水吧。

  • 17
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
面试官:怎么做jdk8的垃圾收集器的调优(面试常问)。 对于jdk8的垃圾收集器的调优,需要我们了解垃圾收集器的工作原理和相关的参数配置。下面我将介绍几个常见的调优方法。 首先,我们可以通过选择合适的垃圾收集器来优化性能。jdk8提供了多个垃圾收集器,如Serial收集器、Parallel收集器CMS收集器等。根据应用的特性和需求,选择合适的垃圾收集器可以提高垃圾回收效率。 其次,我们可以调整垃圾收集器的参数来达到更好的性能。例如,可以通过设置-Xms和-Xmx参数调整堆的大小,合理分配内存资源。同时,可以通过设置-Xmn参数来调整新生代的大小,以及通过-XX:NewRatio参数来调整新生代和老年代的比例。 另外,我们还可以使用GC日志来分析垃圾收集器的行为。通过打开GC日志并使用工具分析日志信息,可以获取到各个垃圾收集阶段的耗时、吞吐量等指标,从而更好地进行优化。 此外,jdk8还引入了元空间(Metaspace)来替代永久代(PermGen),我们可以通过调整-XX:MetaspaceSize和-XX:MaxMetaspaceSize参数来优化元空间的大小,以减少垃圾回收的次数。 最后,我们还可以考虑使用一些第三方工具来辅助调优,如VisualVM和jstat等。这些工具可以提供更详细的性能信息和可视化界面,帮助我们更全面地进行垃圾收集器的调优工作。 总结而言,对于jdk8的垃圾收集器的调优,我们需要理解垃圾收集器的工作原理和配置参数,并根据应用的需求和特点选择合适的垃圾收集器。通过调整参数、分析GC日志以及使用辅助工具,我们可以优化垃圾收集器的性能,提高应用的运行效率。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值