在上篇文章中介绍了Java GC:基础原理,这篇文章来看下在 JVM 中是如何实现具体的收集器的。git
JVM 提供了多种垃圾收集器用来分别收集新生代和老年代,新老收集器之间能够组合使用,可是在实际使用中(基于Java 8),通常常见的有4种收集器组合github
PS: 在 Java 8 中 -XX:+UseParallelOldGC 和 -XX:+UseParNewGC 已经没法单独使用算法
下面咱们从原理上去理解每一个收集器是如何工做的,但并不会去细究具体的实现。数据结构
Serial GC 使用 mark-copy 算法处理新生代,使用 mark-sweep-compact 算法处理老年代。正如名字所说的那样,这是一个单线程的收集器。整个收集过程会触发 stop-the-world 暂停应用,直到回收结束。该收集器适用于只有几百M大小的堆和单核 CPU的状况。对于服务端,不多会使用这个组合,由于没法合理的使用计算机资源,不过反过来讲的话,这也能够知足对系统资源使用有限制的状况。多线程
Parallel GC 和 Serial GC 使用的算法同样。只不过是多线程的,是 JVM 默认使用的 GC 。和 Serial GC 同样,在整个收集过程会触发 stop-the-world 暂停应用,直到回收结束。在多核环境下,处理速度要比 Serial GC 快,能够提升吞吐量。并发
Serial GC 和 Parallel GC 在回收垃圾的过程当中须要暂停应用线程,所以若是对应用延迟有要求的话,CMS 收集器是一个更好的选择。其使用 stop-the-world 的 mark-copy 算法收集新生代(ParNew,和 Parallel GC 类似但不兼容,只和 CMS 一块儿使用),使用几乎和应用线程并发的 mark-sweep 算法收集老年代。post
PS :至于 ParNew 和 Parallel 的关系是有历史缘由的,有兴趣的能够看这个为何有Parallel GC 了,还要有个 ParNew,还不兼容?线程
CMS 主要用于避免老年代回收时长时间的暂停。首先,其使用了 mark-sweep 算法而不进行压缩(注意因为会产生内存碎片,所以在内存分配上使用了 free-lists 而不是指针碰撞,因此分配速度会相对慢一些) 。其次,在 mark-sweep 算法收集过程当中,与应用程序几乎是并发的,几乎不影响应用程序的执行(暂停的时间很短)。设计
PS:CMS为何没有采用标记-整理算法来实现?3d
不过凡事有利即有弊
CMS 的并发收集过程以下:
此外,除了被动的由 Mionr GC触发 Old GC 外,在实际应用中,还存在着主动的,周期性的 Old GC,只不过存在着触发条件,有兴趣的能够自行额外查阅。
虽然 JDK 9 已经废除 CMS 垃圾收集器,但 CMS 仍然是 JDK 8 及之前的追求低延迟的不错的可选项 。
为解决 CMS 算法产生的一系列问题缺陷,HotSpot提供了另一种垃圾回收策略,从 JDK 9 开始,G1 取代 Parallel GC 成为 JVM 中的默认 GC。G1 的其中一个核心目标是使 stop-the-world 的时间可预测而且可配置。好比说,你能够要求每分钟 stop-the-world 的暂停时间不能超过 5 毫秒,G1 会尽最大可能去完成需求,但并非必定能达到的。
不像以前的堆内存是按分代连续分布的,G1 将堆划分红了必定数量(典型的如2048个)的小区域(Region),每一个小区域多是 Eden, Survior 或者 Old 区域。在逻辑上,Eden 和 Survivor 区域组成了新生代,Old 区域组成了老年代。这个设计容许 GC 每一个只收集其中的一些区域而不是整个堆。不过新生代区域每次都会参与。另外一个比较新奇的特色是 G1 能够估计每一个区域存活对象的多少,存活数量多的区域会先被收集,这也是这个收集器名字的由来,garbage first 收集器。
G1 大体可分为 Young GC 和 Mixed GC,Young GC 处理全部的 Young Region, Mixed GC 处理全部的 Young Region 和 部分的 Old Region,至于选择哪些 Old Region,须要并发标记来搜集必要的信息。分代式G1的正常工做流程就是在 young GC 与 mixed GC 之间视状况切换,背后按期作并发标记搜集资料。
在应用程序刚开始的时候,G1 没有额外的信息去运行并发阶段。所以在这个阶段,G1 的运做模式和其余的新生代收集器很像,须要 stop-the-world,使用复制算法,将存活对象的复制到 Survivor 区域中,或者空闲区域中(以后这个区域也是 Survivor 区)。该阶段能够理解为 Young GC (Minor GC),所谓疏散,其实就是 copy 到其余区域中。
G1 收集器的不少概念是创建在 CMS 上的,所以在流程上会有一些类似。尽管如此,它们之间也有不少不同的地方,好比 CMS 则是采用增量更新的方式,即额外 mark 修改的引用再作处理,而 G1 使用了 SATB (Snapshot-At-The-Beginning) 来维持并发 GC 的正确性 。Snapshot-At-The-Beginning,从字面上理解,就是在 GC 开始时产生一个全部存活对象的逻辑快照,在这个快照中存活的对象,加上以后在 GC 过程当中新分配的对象认为是最终的存活对象,其它不可到达的对象就是死的了。关于 SATB 具体是如何工做的,能够看R大写过的 G1 讲解
和 CMS 同样,该阶段具体的能够分为如下几个阶段,每一个阶段的目的和 CMS 差很少,只是具体实现上会有区别:
其中 Initial Mark 在 Evacuation Pause 中捎带完成了。注意这里的 cleanup,并非真的在堆上清理了实际对象,而是统计每一个 region 存活数量的多少,并按预期的 GC 效率对它们进行排序,为 mixed GC 作准备。但有例外,若是发现这个 region 都没有存活对象,整个区域会被回收到可分配的 region 列表中。
这个阶段并不必定紧跟在并发标记以后,须要知足必定的条件,好比,若是能够同时释放一大部分的老年代,不然该阶段没有必要。所以,在并发标记结束和混合疏散暂停之间可能很容易出现一些只回收年轻的疏散阶段。
该阶段选定全部新生代里的 region,外加根据并发标记统计得出收集收益高的若干个老年代 region(在用户指定的开销目标范围内尽量选择收益高的old gen region)进行回收。