所有分类
  • 所有分类
  • 未分类

JVM-CMS和G1垃圾回收器的区别和执行流程

简介

本文介绍CMS和G1的垃圾回收流程以及它们的区别。

CMS与G1的区别

CMS(Concurrent Mark SweepG1(Garbage-First
目的减少回收停顿时间(牺牲吞吐量)。减少长停顿的次数、增加吞吐量。 为了替代CMS(JDK11默认使用G1)。
回收的代回收老年代回收新生代和老年代
算法标记-清除从整体来看是“标记-整理”算法; 从局部上来看“复制”算法
内存碎片会产生内存碎片不会产生内存碎片
过程初始标记=> 并发标记=> 重新标记=> 并发清理初始标记=> 并发标记=> 重新标记=> 并行清理
使用场景服务器资源较少(处理器少、内存小)。服务器资源较多(多核处理器、大内存。)
想消除长时间的GC停顿(超过0.5-1秒的停顿)。

CMS 在小内存应用上的表现要优于 G1,而大内存应用上 G1 更有优势,大小内存的界限是6GB到8GB。

CMS垃圾回收器

流程

说明

CMS是老年代并发GC算法:(ConcurrentMark-SweepGC、CMS GC)。

算法总体逻辑:标记-清除(Mark-Sweep)

流程

总体流程为:初始标记=> 并发标记=> 重新标记=> 并发清理。

详细流程如下:

  1. 初始标记(Initial Mark)(STW
    1. 此阶段会暂停虚拟机(STW),由根对象扫描出所有的关联对象,并做出标记。此过程只会导致JVM短暂暂停。
  2. 并发标记(Concurrent Marking)
    1. 恢复所有暂停的线程对象,并且对之前标记过的对象进行扫描,取得所有跟标记对象有关联的对象。
  3. 并发预清理(Concurrent Precleaning)
    1. 査找所有在并发标记阶段新进入老年代的对象(例如一些对象可能从新生代晋升到老年代,或者有一些对象被分配到老年代)通过重新扫描,减少下一阶段的工作。
  4. 重新标记(Remark)(STW
    1. 此阶段会暂停虚拟机(STW),对在并发标记阶段被改变的引用或新创建的对象进行标记。
  5. 并发清理(Concurrent Sweeping)
    1. 恢复所有暂停的应用线程,对所有未标记的垃圾对象进行清理,并且尽量将己回收对象的空间重新拼凑为一个整体。在此阶段收集器线程和应用程序线程并发执行。
  6. 并发重置(Concurrent Reset)
    1. 重置CMS收集器的数据结构,等待下一次垃圾回收。

优点

  1. 支持并发收集。
  2. 低停顿。
    1. 垃圾收集过程中最耗时的并发标记和并发清除过程,CMS可以在这两个过程中和用户线程一起工作。所以,用户线程在这个时候就不用停下来了。

缺点

  1. 对CPU非常敏感。
    1. 在并发阶段虽然不会导致用户线程停顿,但是会占用了一部分线程,若CPU资源不足会使应用程序变慢。
  2. 无法处理浮动垃圾。
    1. 在最后一步并发清理过程中,用户线程执行也会产生垃圾,但是这部分垃圾是在标记之后,所以只有等到下一次gc的时候清理掉,这部分垃圾叫浮动垃圾。
    2. 由于并发清理的时候,用户线程也在运行,就需要保证用户线程在运行的时候需要留有部分内存以供使用。但是当这部分内存不足以给用户线程正常使用时,就会出现一次 “Concurrent Mode Failure”,一旦出现了“Concurrent Mode Failure”,便会开启后备方案,临时使用SerialOld收集器进行收集工作。
  3. CMS使用“标记-清理”算法会产生大量的空间碎片。
    1. 当碎片过多,将会给大对象空间的分配带来很大的麻烦,往往会出现老年代还有很大的空间但无法找到足够大的连续空间来分配当前对象,不得不提前触发一次FullGC。
  4. 新的JDK会逐渐移除CMS
    1. JDK9:将CMS标记为废弃(Depracated)。
    2. JDK14:直接删除CMS

G1垃圾回收器

流程

说明

G1(Garbage-First)可以收集回收新生代和老年代。

G1垃圾收集器采用的是区域化、分布式的垃圾收集器。其核心思想是将整个堆内存区域划分成大小相同的子区域(Region),在JVM启动时会自动设置这些子区域的大小(区域大小范围为1MB〜32MB,最多可以设置2048个区域,即支持的最大内存为32MB*2048=65536MB=64GB内存),这样Eden、Survivor、Tenured就变为了一系列不连续的内存区域,也就避免了全内存区的GC操作。G1收集器的内存分配如图1所示。

新生代的GC流程

保存空间的大小会根据之前的对象大小进行重新计算。计算与对象的复制过程中依然会产生很短暂的暂停(STW),并且整个回收过程中会有多个回收线程并发收集。收集流程如图2所示:

老年代的GC流程

G1的老年代GC操作流程与CMS类似,并且在整个回收过程中依然会产生短暂的停顿。

流程为:初始标记(STW)=> 并发标记=> 重新标记(STW)=> 并行清理(STW)

  1. 初始标记(STW
  2. 根区域(Region)扫描
    1. 在初始标记的存活区扫描对老年代的引用,并且对相关引用对象进行标记,该阶段与其他应用线程(非STW)同时运行。只有完成该阶段后,才能开始下一次STW年轻代垃圾回收。
  3. 并发标记
    1. 在堆内存中进行并发标记(与其他应用线程同时运行),在此过程中有可能被年轻代GC打断。
    2. 在此阶段,如果发现某一区域内全部为垃圾对象,那么会立即回收此区域的内存空间。而在此阶段也会计算每个区域的对象活跃度(该区域中存活对象的比例)。
  4. 重新标记(STW
    1. 此阶段主要是用于收集并发标记阶段产生的垃圾空间产生短暂停顿(STW)。
    2. G1收集器对该阶段使用了比CMS更高效的初始快照算法SATB(Snapshot-At-The-Beginning)
  5. 并行清理(STW
    1. 清理所有标记的垃圾内存空间,此阶段会产生短暂停顿(STW)。
    2. 此阶段会清除记录集合(RememberSets)并将空白区域重置。
  6. 复制阶段
    1. 将回收区域的存活对象复制到没有使用过的新区域(Region)。
图1:初始化标记阶段
图2:并发标记阶段
图3:重新标记阶段
图4:复制阶段

当整个操作执行完成后的内存关系如图5所示:

图5:G1老年代内存垃圾收集结束

特点

1、并行与并发

G1能充分利用CPU、多核环境下的硬件优势,使用多个CPU(CPU或者CPU核心)来缩短stop-The-World停顿时间。部分其他收集器原本需要停顿Java线程执行的GC动作,G1收集器仍然可以通过并发的方式让java程序继续执行。

2、分代收集

分代概念在G1中依然得以保留。虽然G1可以不需要其它收集器配合就能独立管理整个GC堆,但它能够采用不同的方式去处理新创建的对象、已经存活了一段时间、熬过多次GC的旧对象以获取更好的收集效果。也就是说G1可以自己管理新生代和老年代了。

3、不产生内存碎片

由于G1使用了独立区域(Region)概念,G1从整体来看是基于“标记-整理”算法实现收集,从局部(两个Region)上来看是基于“复制”算法实现的,但无论如何,这两种算法都意味着G1运作期间不会产生内存空间碎片。

4、可预测的停顿

降低停顿时间是G1和CMS共同的关注点,但G1除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用这明确指定一个长度为M毫秒的时间段内,消耗在垃圾收集上的时间不得超过N毫秒。

优点

  1. 不产生内存碎片
  2. 可指定最大停顿时间
    1. 这样可以让系统很少有较长的停顿时间。
      1. 对于短的停顿时间:G1 未必比 CMS短
      2. 对于长的停顿时间:G1 的长的停顿时间的次数比CMS少很多。

缺点

  1. 内存占用高
    1. 堆内存被划分为许多个小的Region分区数量,面对跨Region对象引用问题,每个Region分区都需要独立维护一份记忆集,使得用于维持G1正常运行的额外内存空间占到了总堆内存空间的10%~20%。
  2. 执行负载高
    1. CMS用写后屏障来更新维护卡表
    2. G1除了使用写后屏障来更新维护卡表外,为了实现原始快照搜索算法,还使用写前屏障来跟踪并发时的指针变化情况。
0

评论0

请先

显示验证码
没有账号?注册  忘记密码?

社交账号快速登录