Loading

CMS收集器 & G1收集器

😉 本文共3412字,阅读时间约6min

CMS 收集器

Concurrent Mark Sweep,并发标记清除。一种以获取最短回收停顿时间为目标的老年代收集器。

特点

老年代收集器,基于标记-清除算法实现。并发收集、低停顿,但是会产生内存碎片。

应用场景:适用于注重服务的响应速度,希望系统停顿时间最短,给用户带来更好的体验等场景下。如 web 程序、b/s 服务。

  • CMS 收集器的内存回收过程是与用户线程一起并发执行的
    • 可以搭配 ParNew 收集器(多线程,新生代,复制算法)
    • Serial Old 收集器(单线程,老年代,标记-整理算法)使用(并发失败,退化为需要STW的Serial Old)。

CMS与三色标记算法

初始标记、并发标记、重新标记、并发清理

三色标记法

为什么CMS不作为JDK默认垃圾回收器?

存在两个致命问题。内存碎片问题(promotion failed)和浮动垃圾问题(concurrent mode failure)

内存碎片问题(promotion failed)

出现 promotion failed 一般指从年轻代 晋升至 老年代失败,一般都是内存碎片导致连续空间不足以放下新的对象数据。

因为CMS采用的是 标记清除的方式,所以内存碎片会比较多,采用标记清除的方式是需要维护一个空闲列表去分配对象 ,一旦内存的连续空间不足以放新产生的对象的时候,CMS会使用Serial Old串行垃圾回收器 清理并且整理老年代垃圾。

Serial Old垃圾回收器是单线程标记整理的垃圾回收器 所以一旦CMS采用了Serial Old方式去整理老年代垃圾,那么最后STW的时间可能无法预估,单线程串行回收且整理垃圾只适用于几M的内存大小,那么一旦老年代内存特别大 使用Serial Old 后果将不堪设想 可能会几小时 或者 更久。

解决这个问题的办法就是:

  1. 提前触发CMS
  2. 可以让CMS在进行一定次数的Full GC(标记清除)的时候进行一次标记整理算法,CMS提供了以下参数来控制:
-XX:UseCMSCompactAtFullCollection -XX:CMSFullGCBeforeCompaction=5

浮动垃圾问题(concurrent mode failure)

什么是浮动垃圾?

在CMS的并发标记和并发清理阶段,用户线程是还在继续运行的,程序在运行自然就还会伴随有新的垃圾对象不断产生,而这部分的垃圾对象是出现在标记过程结束以后,CMS无法在当次收集中处理掉它们,只好留在下次垃圾收集时再清理掉。这样的垃圾就叫做浮动垃圾。

并发标记:GC Roots的漏标未完全解决。

CMS是采用增量写屏障,增量写屏障只会记录新增加的引用 而不会记录删除的引用,比如在并发标记阶段 删除了某个GCRoots的引用,就算有重新标记但是在重新标记的时候并不知道删除了某个引用,当然在并发清理阶段再次产生的垃圾也是属于浮动垃圾。

image-20230117171033473

Concurrent Mode Failure

CMS垃圾回收器 使用一个或多个与应用程序线程同时运行的垃圾收集器线程,目的是在年老代变满之前完成它的收集,如果在并发清理的同时无法即时回收老年代空间(浮动垃圾),并且同时程序线程又产生大量的数据导致老年代在没有清理完成的时候又再次被填满,也即是无法在老年代填满之前回收掉之前的垃圾数据,则会抛出 Concurrent Mode Failure并发收集错误 出现Concurrent Mode Failure

解决方式 同样也是提前触发CMS

回收阈值 通过修改调低CMSInitiatingOccupancyFraction的值 ,但是也不能太低,太低会导致频繁的FGC 。

CMS被弃用

因为上面所说的CMS缺点以及本身机制存在的问题,所以JVM重新实现了G1的垃圾回收器去替换CMS垃圾回收器,在JDK14版本彻底的删除了CMS,也是JVM彻底删除的第一款垃圾回收器。

G1 收集器

定义: Garbage First
适用场景:

  • 同时注重吞吐量和低延迟(响应时间)
  • 超大堆内存(内存大的),会将堆内存划分为多个大小相等的区域
  • 整体上是标记-整理算法,两个区域之间是复制算法


垃圾回收器之 G1 垃圾回收器

GC调优

调优领域

  • 内存
  • 锁竞争
  • cpu 占用
  • io
  • gc

确定目标

低延迟/高吞吐量? 选择合适的GC

  • CMS G1 ZGC
  • ParallelGC
  • Zing

3)最快的 GC

首先排除减少因为自身编写的代码而引发的内存问题

  • 查看 Full GC 前后的内存占用,考虑以下几个问题
    • 数据是不是太多?
      • resultSet = statement.executeQuery("select * from 大表 limit n")
    • 数据表示是否太臃肿
      • 对象图
      • 对象大小 16 Integer 24 int 4
    • 是否存在内存泄漏
      • static Map map ...

      • 第三方缓存实现

新生代调优

  • 新生代的特点
    • 所有的 new 操作分配内存都是非常廉价的
      • TLAB thread-lcoal allocation buffer
    • 死亡对象回收零代价
    • 大部分对象用过即死(朝生夕死)
    • Minor GC 所用时间远小于 Full GC
  • 新生代内存越大越好么?
    • 不是
      • 新生代内存太小:频繁触发 Minor GC ,会 STW ,会使得吞吐量下降
      • 新生代内存太大:老年代内存占比有所降低,会更频繁地触发 Full GC。而且触发 Minor GC 时,清理新生代所花费的时间会更长
    • 新生代内存设置为内容纳[并发量*(请求-响应)]的数据为宜
  • 幸存区需要能够保存 当前活跃对象+需要晋升的对象
  • 晋升阈值配置得当,让长时间存活的对象尽快晋升
-XX:MaxTenuringThreshold=threshold
-XX:+PrintTenuringDistrubution

老年代调优

以 CMS 为例:

  • CMS 的老年代内存越大越好
  • 先尝试不做调优,如果没有 Full GC 那么已经,否则先尝试调优新生代。
  • 观察发现 Full GC 时老年代内存占用,将老年代内存预设调大 1/4 ~ 1/3
-XX:CMSInitiatingOccupancyFraction=percent

案例

案例1:Full GC 和 Minor GC 频繁
案例2:请求高峰期发生 Full GC,单次暂停时间特别长(CMS)
案例3:老年代充裕情况下,发生 Full GC(jdk1.7)

posted @ 2023-01-17 18:07  iterationjia  阅读(247)  评论(0编辑  收藏  举报