CMS (Concurrent Mark Sweep) 收集器
- CMS 是一种以最短停顿时间为目标的收集器,使用CMS并不能达到GC效率最高(总体GC时间最小),但它能尽可能降低GC时服务的停顿时间, CMS收集器使用的是标记-清除算法
- 特点
- 最求最短停顿时间,非常适合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引用或者被年轻代存活对象所引用的所有对象
- Phase 2: Concurrent Mark (并发标记)
- 在这个阶段 Garbage Collector 会遍历老年代,然后标记所有存活的对象,它会更具上个阶段找到的GC Roots 遍历查找。并发标记阶段, 他会与用户的应用程序并发运行。 并不是老年代所有的存活对象都会被标记, 因为在标记期间用户的程序可能会改变一些引用
- 在上面的图中,与阶段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 的相关核心参数
- -XX:+UseConcmarkSweepGC: 启用 cms。
- -XX:ConcGCThreads: 并发的 GC 线程数。
- -XX:+UseCMSCompactAFFullCollection: Full GC 之后做压缩整理(减少碎片)。
- -XX:CMSFullGCsBeforeCompaction: 多少次 Full GC 之后压缩一次,默认是0,代表每次 Full GC 后都会压缩一次。
- -XX:CMSInitiatingOccupancyFraction: 当老年代使用达到该比例时会触发 Full GC (默认是92,这是百分比)。
- -XX:+UsecmslnitiatingOccupancyOnly: 只使用设定的回收阈值(-XX:CMSInitiatingOccupancyFraction设定的值),如果不指定,JVM仅在第一次使用设定值,后续则会自动调整。
- -XX:+CMSScavengeBeforeRemark: 在 CMS GC前启动一次 minor gc,目的在于减少老年代对年轻代的引用,降低 CMS GCI的标记阶段时的开销般 CMS 的GC耗时 80% 都在标记阶段。
- -XX:+CMSParallellnitialMarkEnabled: 表示在初始标记的时候多线程执行, 缩短 STW。
- -XX:+CMSParallelRemarkEnabled: 在重新标记的时候多线程执行, 缩短 STW。
垃圾回收算法
三色标标记算法
提到并发标记,我们不得不了解并发标记的三色标记算法。它是描述追踪式回收器的一种有效的方法,利用它可以推演回收器的正确性。
我们将对象分为三种类型:
- 黑色:根对象,或者该对象与它的子对象都被扫描过(对象被标记了,且它的所有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, 将所有即将被删除的引用关系的旧引用记录下来,最后这些旧引用为根重新扫描一遍