JVM 垃圾收集算法和垃圾收集器(中)

简介: 本文主要讲述垃圾收集算法和常用的几种垃圾收集器

CMS (Concurrent Mark Sweep) 收集器


  • CMS 是一种以最短停顿时间为目标的收集器,使用CMS并不能达到GC效率最高(总体GC时间最小),但它能尽可能降低GC时服务的停顿时间, CMS收集器使用的是标记-清除算法

image.png


  • 特点


  • 最求最短停顿时间,非常适合Web应用
  • 针对老年代,一般结合ParNew使用
  • Concurrent, GC 线程和用户线程并发工作(尽量并发)
  • Mark-Sweep
  • 只有在多CPU环境下才有意义
  • 使用-XX:+UseConcMarkSweepGC启用


  • CMS收集器缺点


  • CMS以牺牲CPU资源的代价来减少用户线程的停顿。当CU个数小于4的时候,可能对吞吐量影响非常大
  • CMS在并发清理的过程中,用户线程还在跑,这时候需要预留一部分空间给用户线程。
  • CMS用Mark-Sweep, 会带来碎片问题。碎片过多的时候会容易平凡触发Full GC


CMS 收集器执行步骤


  • CMS 收集器执行步骤总览


  • Phase 1: Initial Mark
  • Phase 2: Concurrent Mark
  • Phase 3: Concurrent Preclean
  • Phase 4: Concurrent Abortable Preclean
  • Phase 5: Final Remark
  • Phase 6: Concurrent Sweep
  • Phase 7: Concurrent Reset


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


  • 这个是CMS两次 stop-the-world 事件的其中一次,这个阶段的目标是:标记哪些直接被GC Root引用或者被年轻代存活对象所引用的所有对象


image.png


  • Phase 2: Concurrent Mark (并发标记)


  • 在这个阶段 Garbage Collector 会遍历老年代,然后标记所有存活的对象,它会更具上个阶段找到的GC Roots 遍历查找。并发标记阶段, 他会与用户的应用程序并发运行。 并不是老年代所有的存活对象都会被标记, 因为在标记期间用户的程序可能会改变一些引用


image.png


  • 在上面的图中,与阶段1的图进行对比就会发现有一个对象的引用已经发生额变化。


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


  • 这个也是一个并发阶段,与应用的线程并发运行,并不会 stop 应用的线程。在并发运行的过程中,一些对象可能会发生变化,但是这种情况发生时。JVM 将会包含这个对象的区域(Card)标记为 Dirty, 这也就是 Card Marking.
  • 在 pre-clean 阶段,哪些能够从 Dirty 对象到达的对象也会被标记,这个标记做完后,dirty card 标记就会被清除了。


  • Phase 4: Concurrent Abortable Preclean (并发预先可能失败的清理)


  • 这也是一个并发阶段,但是同样不会影响用户的应用线程,这个阶段是为了尽量承担 STW (stop-the-world)中最终标记阶段的工作。这个阶段持续时间依赖于很多的因素,由于这个阶段是在重复做很多相同的工作,直接满足一些条件(比如:重复迭代的次数、完成的工作量或者时钟时间等)。


  • Phase 5: Final Remark (最终重新标记)


  • 这个阶段是第二个STW阶段,也是 CMS的最后一个,这个阶段的目标是标记老年代所有的存活对象,由于之前的阶段是并发执行的,GC线程可能跟不上应用程序的变化,为了完成标记老年代所有存活对象的目标,STW就非常有必要了
  • 通常CMS的Final Remark 阶段会在年轻代尽可能干净的时候运行,目的是为了减少STW发生的可能性(年轻代存回对象过多的话,也会导致老年代设计的存活对象会很多)。这个阶段会比前面几个阶段更复杂一些。
  • 标记阶段完成,经历过这5个阶段之后,老年代所有存活的对象都被标记过了,现在可以通过清除算法去清理哪些老年代不再使用的对象。
  • 这个阶段的时间一般比初始标记的时间稍长,远远比并发标记的时间短,主要用到三色标记里面的增量更新算法


  • Phase 6: Concurrent Sweep (并发清理)


  • 这里不需要STW,它是与用户的应用程序并发运行的,这个阶段是:清除哪些不再使用的对象,回收他们占用的空间为将来使用。


  • Phase 7: Concurrent Reset (并发重置)


  • 这个阶段是并发执行的,它会重设 CMS 内部的数据结构,为下次的GC做准备。


CMS 的相关核心参数


  1. -XX:+UseConcmarkSweepGC: 启用 cms。


  1. -XX:ConcGCThreads: 并发的 GC 线程数。


  1. -XX:+UseCMSCompactAFFullCollection: Full GC 之后做压缩整理(减少碎片)。


  1. -XX:CMSFullGCsBeforeCompaction: 多少次 Full GC 之后压缩一次,默认是0,代表每次 Full GC 后都会压缩一次。


  1. -XX:CMSInitiatingOccupancyFraction: 当老年代使用达到该比例时会触发 Full GC (默认是92,这是百分比)。


  1. -XX:+UsecmslnitiatingOccupancyOnly: 只使用设定的回收阈值(-XX:CMSInitiatingOccupancyFraction设定的值),如果不指定,JVM仅在第一次使用设定值,后续则会自动调整。


  1. -XX:+CMSScavengeBeforeRemark: 在 CMS GC前启动一次 minor gc,目的在于减少老年代对年轻代的引用,降低 CMS GCI的标记阶段时的开销般 CMS 的GC耗时 80% 都在标记阶段。


  1. -XX:+CMSParallellnitialMarkEnabled: 表示在初始标记的时候多线程执行, 缩短 STW。


  1. -XX:+CMSParallelRemarkEnabled: 在重新标记的时候多线程执行, 缩短 STW。


垃圾回收算法


三色标标记算法


提到并发标记,我们不得不了解并发标记的三色标记算法。它是描述追踪式回收器的一种有效的方法,利用它可以推演回收器的正确性。


image.png


我们将对象分为三种类型:


  • 黑色:根对象,或者该对象与它的子对象都被扫描过(对象被标记了,且它的所有field也被标记完了)


  • 灰色:对象本身被扫描,还没有扫描完该对象中的子对象(它的field还没有被标记或标记完)


  • 白色:未被扫描对象,扫描完成所有对象之后,最终为白色的为不可达对象,即垃圾对象(对象没有被标记到)


三色标记过程


  • 第一步,三色标记算法,如果将根对象设置为黑色,那么下级节点的为灰色,再下面的的为白色


  • 第二步,灰色扫描完毕后,那么剩下的白色变为灰色


  • 第三步,灰色扫描完毕后,那么全部被标记为黑色,不可达的还是为白色


三色标记算法的对象丢失


  • 但是如果在标记过程中,应用程序也进行,那么对象的指针就有可能改变。这样的话,我们就会遇到一个问题:对象丢失


  • 例子:


  • 第一步,初始 Root(黑)-> A(黑) Root(黑)-> B(灰)-> C(白)
  • 第二步,在当前场景下执行如下操作
  • A.c = C
  • B.c = null
  • Root(黑)-> A(黑)-> C(白) Root(黑)-> B(黑)
  • 第三步,如果内存回收的时候,就会将 C 回收掉,会导致 C 对象丢失。


SATB(Snapshot-At-The-Beginning)


  • SATB是维持并发GC的一种手段。G1并发的基础就是SATB。SATB可以理解成在GC开始之前对堆内存里的对象做一次快照,此时活的对象就认为是活的,从而形成一个对象图


  • 在GC收集的时候,新生代的对象也认为是活的对象,除此之外其他不可达的对象也认为是垃圾对象。


  • 如何找到在GC过程分配的对象呢?每个region记录着两个top-at-mark-start(TAMS) 指针,分别为prevTAMS 和nextTAMS。在TAMS以上的独享就是新分配的,因而被视为隐式marked


  • 通过这种方式我们就找到了再GC过程中新分配的对象,并把这些对象认为是活的对象。


  • 解决了对象在GC过程中分配的问题,呢么GC过程中引用繁盛变化的问题是怎么解决的呢?


  • G1给出的解决办法是通过Write Barrier. Write Barrier 就是堆引用字段进行赋值做了额外处理。通过Write Barrier就可以了解到哪些引用对象发生了什么样的变化


  • mark 的过程就是遍历heap标记live object的过程,采用的三色标记算法,这三种颜色为white(表示还未访问到)、gray(访问到但是它用到的引用还诶有完全扫描)、black (访问到而且其用到的引用完全扫描完)


  • 整个三色标记算法就是从GC Roots出发遍历heap,针对可达独享先标记white为gray, 然后再标记gray为black; 遍历完成之后所有可达对象都是black的,所有 white 都是可以回收的。


  • SATB仅仅对于在marking开始阶段进行"snapshot"(marked all reachable at mark start), 但是concurrent 的时候并发修改可能造成对象漏标记


漏标的场景


  • 对black 新引用了一个white对象,然后从gray对象中删除了对该white 对象的引用,这样会造成该white 对象漏标记。


  • 对black 新引用了一个white对象,然后从gray对象删除了一个引用该white对象的white对象,这样也会造成该white对象漏标记。


  • 对black 新引用了一个刚new出来的一个white对象,没有其他的gray对象引用该white对象,这样也会造成该white对象漏标记。


  • 对于三色算法在concurrent的时候可能产生漏标的问题,SATB在marking阶段中,对于从gray对象移除的目标引用对象标记为gray, 对于从gray对象移除的 目标引用对象标记为gray,对于black引用的新产生的对象标记为black; 由于是在开始的时候进行snapshot, 因而可能存在Floating Garbage


漏标和误标


  • 误标识没有关系,顶多造成浮动垃圾,在下次GC还是可以回收的,但是漏标的后果是致命的把本应该存活的对象给回收了,从而影响程序的正确性。


  • 漏标的情况只会发生在白色对象中,且满足一下任意一个条件


  • 并发标记时,应用线程给一个黑色独享的引用类型字段赋值了该白色对象
  • 并发标记时,应用线程删除所有灰色对象到该白色对象的引用


  • 第一种情况,利用post-write barrier 记录所有新增的引用关系,然后根据这些引用关系为根重新扫描一遍


  • 对于第二种情况,利用pre-write barrier, 将所有即将被删除的引用关系的旧引用记录下来,最后这些旧引用为根重新扫描一遍



相关文章
|
5天前
|
存储 算法 Java
深入浅出JVM(十八)之并发垃圾收集器G1
深入浅出JVM(十八)之并发垃圾收集器G1
|
5天前
|
存储 算法 Java
深入浅出JVM(十七)之并发垃圾收集器CMS
深入浅出JVM(十七)之并发垃圾收集器CMS
|
5天前
|
算法 Java
深入浅出JVM(十五)之垃圾收集器(上篇)
深入浅出JVM(十五)之垃圾收集器(上篇)
|
5天前
|
安全 算法 Java
深入浅出JVM(十三)之垃圾回收算法细节
深入浅出JVM(十三)之垃圾回收算法细节
|
5天前
|
存储 算法 Java
深入浅出JVM(十二)之垃圾回收算法
深入浅出JVM(十二)之垃圾回收算法
|
5天前
|
算法 Java PHP
JVM 的垃圾回收机制以及垃圾回收算法的详解
JVM 的垃圾回收机制以及垃圾回收算法的详解
10 0
|
7天前
|
Arthas 监控 算法
JVM工作原理与实战(二十五):堆的垃圾回收-垃圾回收算法
JVM作为Java程序的运行环境,其负责解释和执行字节码,管理内存,确保安全,支持多线程和提供性能监控工具,以及确保程序的跨平台运行。本文主要介绍了垃圾回收算法评价标准、标记清除算法、复制算法、标记整理算法、分代垃圾回收算法等内容。
19 0
JVM工作原理与实战(二十五):堆的垃圾回收-垃圾回收算法
|
20天前
|
算法 Java
JVM 垃圾回收算法(重要)
JVM 垃圾回收算法(重要)
|
1月前
|
存储 缓存 算法
深度解析JVM世界:垃圾判断和垃圾回收算法
深度解析JVM世界:垃圾判断和垃圾回收算法
|
1月前
|
算法 Java UED
【五一创作】值得一看的JVM垃圾收集器
【五一创作】值得一看的JVM垃圾收集器