垃圾回收器
1. 垃圾回收器分类标准
2. 七种垃圾回收器概述
在 JVM
中,具体实现有 Serial
、ParNew
、Parallel Scavenge
、CMS
、Serial Old(MSC)
、Parallel Old
、G1
等。在下图中,你可以看到 不同垃圾回收器 适合于 不同的内存区域,如果两个垃圾回收器之间 存在连线,那么表示两者可以 配合使用。
如果当 垃圾回收器 进行垃圾清理时,必须 暂停 其他所有的 工作线程,直到它完全收集结束。我们称这种需要暂停工作线程才能进行清理的策略为 Stop-the-World
。以上回收器中, Serial
、ParNew
、Parallel Scavenge
、Serial Old
、Parallel Old
均采用的是 Stop-the-World
的策略。
图中有 7
种不同的 垃圾回收器,它们分别用于不同分代的垃圾回收。
-
新生代回收器:Serial、ParNew、Parallel Scavenge
-
老年代回收器:Serial Old、Parallel Old、CMS
-
整堆回收器:G1
两个 垃圾回收器 之间有连线表示它们可以 搭配使用,可选的搭配方案如下:
新生代 | 老年代 |
---|---|
Serial | Serial Old |
Serial | CMS |
ParNew | Serial Old |
ParNew | CMS |
Parallel Scavenge | Serial Old |
Parallel Scavenge | Parallel Old |
G1 | G1 |
3. 单线程垃圾回收器
3.1. Serial(-XX:+UseSerialGC)
Serial
回收器是最基本的 新生代 垃圾回收器,是 单线程 的垃圾回收器。由于垃圾清理时,Serial
回收器 不存在 线程间的切换,因此,特别是在单 CPU
的环境下,它的 垃圾清除效率 比较高。对于 Client
运行模式的程序,选择 Serial
回收器是一个不错的选择。
Serial
新生代回收器 采用的是 复制算法。
3.2. Serial Old(-XX:+UseSerialGC)
Serial Old
回收器是 Serial
回收器的 老生代版本,属于 单线程回收器,它使用 标记-整理 算法。对于 Server
模式下的虚拟机,在 JDK1.5
及其以前,它常与 Parallel Scavenge
回收器配合使用,达到较好的 吞吐量,另外它也是 CMS
回收器在 Concurrent Mode Failure
时的 后备方案。
Serial
回收器和 Serial Old
回收器的执行效果如下:
Serial Old
老年代回收器 采用的是 标记 - 整理算法。
4. 多线程垃圾回收器(吞吐量优先)
4.1. ParNew(-XX:+UseParNewGC)
ParNew
回收器是在 Serial
回收器的基础上演化而来的,属于 Serial
回收器的 多线程版本,同样运行在 新生代区域。在实现上,两者共用很多代码。在不同运行环境下,根据 CPU
核数,开启 不同的线程数,从而达到 最优 的垃圾回收效果。对于那些 Server
模式的应用程序,如果考虑采用 CMS
作为 老生代回收器 时,ParNew
回收器是一个不错的选择。
ParNew
新生代回收器 采用的是 复制算法。
4.2. Parallel Scavenge(-XX:+UseParallelGC)
和 ParNew
回收一样,Parallel Scavenge
回收器也是运行在 新生代区域,属于 多线程 的回收器。但不同的是,ParNew
回收器是通过控制 垃圾回收 的 线程数 来进行参数调整,而 Parallel Scavenge
回收器更关心的是 程序运行的吞吐量。即一段时间内,用户代码 运行时间占 总运行时间 的百分比。
Parallel Scavenge
新生代回收器 采用的是 复制算法。
4.3. Parallel Old(-XX:+UseParallelOldGC)
Parallel Old
回收器是 Parallel Scavenge
回收器的 老生代版本,属于 多线程回收器,采用 标记-整理算法。Parallel Old
回收器和 Parallel Scavenge
回收器同样考虑了 吞吐量优先 这一指标,非常适合那些 注重吞吐量 和 CPU
资源敏感 的场合。
Parallel Old
老年代回收器 采用的是 标记 - 整理算法。
5. 其他的回收器(停顿时间优先)
5.1. CMS(-XX:+UseConcMarkSweepGC)
CMS(Concurrent Mark Sweep)
回收器是在 最短回收停顿时间 为前提的回收器,属于 多线程回收器,采用 标记-清除算法。
相比之前的回收器,CMS
回收器的运作过程比较复杂,分为四步:
- 初始标记(CMS initial mark)
初始标记 仅仅是标记 GC Roots
内 直接关联 的对象。这个阶段 速度很快,需要 Stop the World
。
- 并发标记(CMS concurrent mark)
并发标记 进行的是 GC Tracing
,从 GC Roots
开始对堆进行 可达性分析,找出 存活对象。
- 重新标记(CMS remark)
重新标记 阶段为了 修正 并发期间由于 用户进行运作 导致的 标记变动 的那一部分对象的 标记记录。这个阶段的 停顿时间 一般会比 初始标记阶段 稍长一些,但远比 并发标记 的时间短,也需要 Stop The World
。
- 并发清除(CMS concurrent sweep)
并发清除 阶段会清除垃圾对象。
初始标记(
CMS initial mark
)和 重新标记(CMS remark
)会导致 用户线程 卡顿,Stop the World
现象发生。
在整个过程中,CMS
回收器的 内存回收 基本上和 用户线程 并发执行,如下所示:
由于 CMS
回收器 并发收集、停顿低,因此有些地方成为 并发低停顿回收器(Concurrent Low Pause Sweep Collector
)。
CMS
回收器的缺点:
- CMS回收器对CPU资源非常依赖
CMS
回收器过分依赖于 多线程环境,默认情况下,开启的 线程数 为(CPU 的数量 + 3)/ 4
,当 CPU
数量少于 4
个时,CMS
对 用户查询 的影响将会很大,因为他们要分出一半的运算能力去 执行回收器线程;
- CMS回收器无法清除浮动垃圾
由于 CMS
回收器 清除已标记的垃圾 (处于最后一个阶段)时,用户线程 还在运行,因此会有新的垃圾产生。但是这部分垃圾 未被标记,在下一次 GC
才能清除,因此被成为 浮动垃圾。
由于 内存回收 和 用户线程 是同时进行的,内存在被 回收 的同时,也在被 分配。当 老生代 中的内存使用超过一定的比例时,系统将会进行 垃圾回收;当 剩余内存 不能满足程序运行要求时,系统将会出现 Concurrent Mode Failure
,临时采用 Serial Old
算法进行 清除,此时的 性能 将会降低。
- 垃圾收集结束后残余大量空间碎片
CMS
回收器采用的 标记清除算法,本身存在垃圾收集结束后残余 大量空间碎片 的缺点。CMS
配合适当的 内存整理策略,在一定程度上可以解决这个问题。
5.2. G1回收器(垃圾区域Region优先)
G1
是 JDK 1.7
中正式投入使用的用于取代 CMS
的 压缩回收器。它虽然没有在物理上隔断 新生代 与 老生代,但是仍然属于 分代垃圾回收器。G1
仍然会区分 年轻代 与 老年代,年轻代依然分有 Eden
区与 Survivor
区。
G1
首先将 堆 分为 大小相等 的 Region
,避免 全区域 的垃圾回收。然后追踪每个 Region
垃圾 堆积的价值大小,在后台维护一个 优先列表,根据允许的回收时间优先回收价值最大的 Region
。同时 G1
采用 Remembered Set
来存放 Region
之间的 对象引用 ,其他回收器中的 新生代 与 老年代 之间的对象引用,从而避免 全堆扫描。G1
的分区示例如下图所示:
这种使用 Region
划分 内存空间 以及有 优先级 的区域回收方式,保证 G1
回收器在有限的时间内可以获得尽可能 高的回收效率。
G1
和 CMS
运作过程有很多相似之处,整个过程也分为 4
个步骤:
- 初始标记(CMS initial mark)
初始标记 仅仅是标记 GC Roots
内 直接关联 的对象。这个阶段 速度很快,需要 Stop the World
。
- 并发标记(CMS concurrent mark)
并发标记 进行的是 GC Tracing
,从 GC Roots
开始对堆进行 可达性分析,找出 存活对象。
- 重新标记(CMS remark)
重新标记 阶段为了 修正 并发期间由于 用户进行运作 导致的 标记变动 的那一部分对象的 标记记录。这个阶段的 停顿时间 一般会比 初始标记阶段 稍长一些,但远比 并发标记 的时间短,也需要 Stop The World
。
- 筛选回收
首先对各个 Region
的 回收价值 和 成本 进行排序,根据用户所期望的 GC
停顿时间 来制定回收计划。这个阶段可以与用户程序一起 并发执行,但是因为只回收一部分 Region
,时间是用户可控制的,而且停顿 用户线程 将大幅提高回收效率。
与其它
GC
回收相比,G1
具备如下4
个特点:
- 并行与并发
使用多个 CPU
来缩短 Stop-the-World
的 停顿时间,部分其他回收器需要停顿 Java
线程执行的 GC
动作,G1
回收器仍然可以通过 并发的方式 让 Java
程序继续执行。
- 分代回收
与其他回收器一样,分代概念 在 G1
中依然得以保留。虽然 G1
可以不需要 其他回收器配合 就能独立管理 整个GC堆,但它能够采用 不同的策略 去处理 新创建的对象 和 已经存活 一段时间、熬过多次 GC
的旧对象,以获取更好的回收效果。新生代 和 老年代 不再是 物理隔离,是多个 大小相等 的独立 Region
。
- 空间整合
与 CMS
的 标记—清理 算法不同,G1
从 整体 来看是基于 标记—整理 算法实现的回收器。从 局部(两个 Region
之间)上来看是基于 复制算法 实现的。
但无论如何,这 两种算法 都意味着 G1
运作期间 不会产生内存空间碎片,回收后能提供规整的可用内存。这种特性有利于程序长时间运行,分配大对象 时不会因为无法找到 连续内存空间 而提前触发 下一次 GC
。
- 可预测的停顿
这是 G1
相对于 CMS
的另一大优势,降低停顿时间 是 G1
和 CMS
共同的关注点。G1
除了追求 低停顿 外,还能建立 可预测 的 停顿时间模型,能让使用者明确指定在一个 长度 为 M
毫秒的 时间片段 内,消耗在 垃圾回收 上的时间不得超过 N
毫秒。(后台维护的 优先列表,优先回收 价值大 的 Region
)。