带你走近 Java 虚拟机到底有哪些垃圾收集器
上一篇文章中我们大致讲了一下Java虚拟机的垃圾收集算法,如果说垃圾收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的实践者。
经典的垃圾收集器
话不多说先上一张各款经典垃圾收集器之间的关系图:
 
 接下来我们一个个进行了解:
Serial 收集器-新生代
Serial 收集器是最基础、历史最悠久的垃圾收集器。
主要特点:
- 它在进行垃圾收集的过程中,必须暂停所有其它工作线程,直到它收集结束。 - Stop The World在这里体现的也是淋漓尽致。
- 比较适合单核处理器或者多核核心数较少的环境下,对比同类收集器而言简单高效;同时是所有垃圾收集器中额外内存消耗最小的 
- 采用的是 - 标记-复制算法 。
垃圾收集过程:
 
 ParNew 收集器-新生代
简单来说它本质上就是 Serial 收集器的多线程并行版本。
主要特点:
- 目前除了 Serial、只有它能与 CMS 收集器配合工作 
- 采用的是 - 标记-复制算法 。
垃圾收集过程
 
 Parallel Scavenge 收集器-新生代
该收集器很多特性和 ParNew 都类似。但是与其它收集器不同的是它关注的点不一样,Parallel Scavenge 收集器关注的是能否达到一个可控制的吞吐量,而 CMS 等收集器关注的是垃圾收集过程中用户线程暂停的时间。
- 吞吐量: 处理器用于运行用户代码的时间与处理器总消耗时间的比值 
 
 主要特点
- 主要是用场景是适合在后台运算而不需要太多交互的分析任务,由于该收集器关注吞吐量所以又被称为 - 吞吐量优先收集器
- 采用的是 - 标记-复制算法 。
精确控制吞吐量的参数
- -XX:MaxGCPauseMillis 控制最大垃圾收集停顿时间 
- -XX:GCTimeRatio 设置吞吐量的大小 
Serial Old 收集器 - 老年代
Serial 收集器的老年代版本主要有两点作用:
- JDK5 之前与 Parallel Scavenge 收集器搭配使用 
- 作为 CMS 收集器发生失败时的后备预案 
主要特点
- 单线程收集器 
- 采用的是 - 标记-整理算法 。
垃圾收集过程
 
 Parallel Old 收集器 - 老年代
支持多线程并发收集,JDK6 之后才提供。它是 Parallel Scavenge 的老年代版本
主要特点
- 采用的是 - 标记-整理算法
垃圾收集过程
 
 CMS 收集器- 老年代
也被称为并发低停顿收集器JDK 5 发布时,HotSpot 推出了一款在强交互应用中几乎可称为具有划时代意义 的垃圾收集器——CMS 收集器;首次实现了让垃圾收集线程与用户线程(基本上)同时工作
主要特点:
- 采用的是 - 标记-清除算法
- 以获取最短回收停顿时间为目标的垃圾收集器,适用于更加关注服务的响应速度的应用需求 
垃圾收集过程
 
 - 初始标记:这里仍需要 - Stop The World
- 并发标记:可以与用户线程一起工作 
- 重新标记:这里仍需要 - Stop The World
- 并发清理:可以与用户线程一起工作 
缺点
所谓的缺点自然也离不开它依赖的算法所带来的弊端
- 在并发阶段,虽然不会导致用户线程停顿,但却会因为占用了一部分线程(或者说处理器计算的能力),从而导致应用程序变慢,降低吞吐量。CMS 默认启动的回收线程数 - (处理器核心数量+3)/4
- 有过缓解方案:增量式并发收集器-JDK9 后被废弃 
- 无法处理 - 浮动垃圾;
- 所谓的浮动垃圾就是 CMS 的并发标记和并发清理阶段,用户线程是还在继续运行的,程序在运行自然就还会伴随有新的垃圾对象不断产生,但这一部分垃圾对象是出现在标记过程结束以后,CMS 无法在当次收集中处理掉它们,只好留待下一次垃圾收集时再清理掉。这一部分垃圾就称为“浮动垃圾”可以通过禅师 - -XX:CMSInitiatingOccu-pancyFraction去进行提高 CMS 触发的百分比设置得太高将会很容易导致大量的并发失败产生,性能反而降低,用户应在生产环境中根据实际应用情况来权衡设置。
- 由于采用的是 - 标记-清除算法,自然也就会有空间碎片化的存在;
- 可以通过 - -XX:+UseCMS-CompactAtFullCollection- -XX:CMSFullGCsBeforeCompaction参数来配置,但是在 JDK9 也被废弃了- -XX:+UseCMS-CompactAtFullCollection:用于在 CMS 收集器不得不进行 Full GC 时开启内存碎片的合并整理过程
- -XX:CMSFullGCsBeforeCompaction:要求 CMS 收集器在执行过若干次(数量由参数值决定)不整理空间的 Full GC 之 后,下一次进入 Full GC 前会先进行碎片整理(默认值为 0,表示每次进入 Full GC 时都 进行碎片整理)。
 
Garbage First 收集器
Garbage First(简称 G1)收集器是垃圾收集器技术发展历史上的里程碑式的成果, 它开创了收集器面向局部收集的设计思路和基于 Region 的内存布局形式。JDK 8 Update 40 后 G1 收集器才被 Oracle 官方称为“全功能的垃圾收集器”(Fully-Featured Garbage Collector)。G1 是一款主要面向服务端应用的垃圾收集器 JDK9 之后 G1 宣告取代 Parallel Scavenge 加 Parallel Old 组合, 成为服务端模式下的默认垃圾收集器,而 CMS 则沦落至被声明为不推荐使用 (Deprecate)的收集器 Region 分区示意图:
 
 垃圾收集过程
 
 - 初始标记 
- 并发标记:出了该阶段外其它阶段也是要暂停用户线程的 
- 最终标记 
- 筛选回收 
主要特点:
- 从 G1 回收器开始,最先进的垃圾收集器的设计导向都不约而同的变为追求能够应付应用的内存分配速率,而不是追求一次将 Java 堆全部清理干净。 
- 关于算法方面:整体上是采用的 - 标记-整理算法,但是如果要是从局部(两个 region)之间采用的- 标记-复制算法。所以这也意味着 G1 垃圾收集器在运行期间不会产生内存空间碎边化的问题。
- 同时用户线程运行过程中,G1 无论是为了垃圾收集产生的内存占用还是程序运行时的额外执行负载都要比 CMS 要高。 
- G1 的卡表设计更为复杂,相比 CMS 的卡表就是比较简单,只有唯一一份,而且只需要处理老年代到新生代的引用 
- G1 由于其特性目前是在大内存应用上能够发挥其优势 
- CMS和G1分别使用增量更新和原始快照技术
TIP:
衡量垃圾收集器的三项重要指标是:内存占用``吞吐量``延迟	
低延迟的垃圾收集器
Shenandoah 收集器
主要特点:
- Shenandoah 收集器摒弃了在 G1 中耗费大量内存和计算资源去维护的记忆集,改用名为 - 连接矩阵的全局数据结构来记录跨 Region 的引用关系,降低了处理跨代指针时的记忆集维护消耗,也降低了伪共享问题发生的概率
- 连接矩阵 
连接矩阵可以简单的理解为一张二维表格,如果 Region N 有对象指向 Region M,就在表哥的 N 行 M 列打上一个标记。
- 整个工作过程全部都是并发的,只有 - 初始标记、最终标记这些阶段有短暂的停顿,这部分停顿的时间基本上是固定的,与堆的容量、堆中对象的数量没有正比例关系
- ZGC 和 Shenandoah 的目标是高度相似的,都希望在尽可能对吞吐量影响不太大的 前提下,实现在任意堆内存大小下都可以把垃圾收集的停顿时间限制在十毫秒以内的低延迟 
垃圾收集过程
 
 - 初始标记 
- 并发标记(重点理解) 
- 最终标记 
- 并发清理 
- 并发回收(重点理解) 
- 初始引用更新 
- 并发引用更新(重点理解) 
- 最终引用更新 
- 并发清理 
ZGC(Z Garbage Collector)收集器
主要特点:
- 内存布局与 Shenandoah、G1 一样采用的 Region 堆内存布局;不同的是 ZGC 的 Region 有些官方资料称为 page 或 ZPage,它们动态性(动态创建和销毁以及动态区域容量大小);ZGC 的 Region 有三种大、中、小容量。 
- 大型 Region:容量不固定,可以动态变化,但是必须是 2MB 的整数倍,由于放置 4MB 或以上的大对象。每个大型 Region 中只存放一个大对象,但是实际容量可能会小于中型 Region,并且最小容量可以低至 4MB,大型 Region 在 ZGC 的实现中是不会被重分配的。 
- 中型 Region(Medium Region):容量固定为 32MB,用于放置大于等于 256KB 但小于 4MB 的对象。 
- 小型 Region(Small Region):容量固定为 2MB,用于放置小雨 256KB 的小对象。 
- ZGC 收集器有一个标志性的设计它采用的是 - 染色指针技术(Colored Pointer)
- 染色指针是一种直接将少量额外的信息存储在指针上的技术 
垃圾收集过程
 
 - 并发标记:遍历对象图做可达性分析的阶段,与 G1、Shenandoah 不同的是 ZGC 的标记是在指针上而不是在对象上,标记阶段会更新染色指针中的 Marked0、Marked1 标志位 
- 并发预备重分配:需要根据特定的查询条件统计出本次收集过程要清理哪些 Region,将这些 Region 组成重分配集(Relocation Set)。重分配集和 G1 的回收集是不一样的。 
- 并发重分配:这个阶段是要把重分配集中的存活对象复制到新的 Region 上,并为重分配集的每个 Region 维护一个转发表(Forward Table)。 
- 并发重映射:主要是做修正整个堆中指向重分配集中旧对象的所有引用,这一点从目标上看是与 Shenandoah 并发引用更新阶段一样的。 
版权声明: 本文为 InfoQ 作者【派大星】的原创文章。
原文链接:【http://xie.infoq.cn/article/3487111fb462ab83efc3d20a9】。文章转载请联系作者。












 
    
评论