Java常用的垃圾收集器

在上一篇文章中,我们介绍了Java的垃圾回收机制,包括什么时候回收垃圾,标记垃圾的算法以及回收垃圾的算法。这篇文章我们主要来介绍Java的垃圾收集器。

在介绍垃圾收集器之前,我们首先需要知道一些必要的概念。

Stop the world

顾名思义,“Stop the world”就是  JVM 由于要执行 GC 而停止了其他应用程序的运行,在任何 GC 算法中都可能会发生。假设有这么一个场景,你的程序正在愉快的运行,突然之间 JVM 要清理垃圾了。然后程序就陷入了10分钟的等待,是不是很抓狂?当然一般情况下会让你等待这么久,但是“Stop the world”会在一定程度上影响用户体验这是毋庸置疑的。所以,多数GC优化通过减少 Stop-the-world 时间来提升系统性能。

垃圾收集器和垃圾回收算法的关系

说完“Stop the world”我们回到正题,回到我们垃圾收集器的模块,如果说垃圾收集算法是内存回收的方法论,那么垃圾收集器那么垃圾收集器就是内存回收的具体实现。

我们费尽心机对垃圾收集器进行比较,目的不是挑出一个最好的垃圾收集器,而是找到最合适的。 现在为止还没有最好的垃圾收集器出现,更没有说出现万能的垃圾收集器,我们能做的就是根据具体应用场景选择适合自己的垃圾收集器。试想一下:如果 “完美垃圾收集器” 真的面世了,我们还需要做这些工作吗?

常用的垃圾收集器

Serial 收集器

Serial收集器是最基本、历史最悠久的垃圾收集器。它是一个单线程收集器,“单线程” 的意义不仅仅意味着它只会使用一条垃圾收集线程去完成垃圾收集工作,更重要的是 它在进行垃圾收集工作的时候必须暂停其他所有的工作线程( "Stop The World" ),直到它收集结束。 它会在用户不可见的情况下把用户正常工作的线程全部停掉。想象一下,当你结束一天的工作回到家中,喝着冰阔乐刷着副本正要打Boss,突然你的电脑说他要休息5分钟,你会是什么感觉?

存在即合理,当然Serial 收集器也有优于其他垃圾收集器的地方,它简单而高效(与其他收集器的单线程相比)。Serial 收集器由于没有线程交互的开销,自然可以获得很高的单线程收集效率。Serial 收集器对于运行在 Client 模式下的虚拟机来说是个不错的选择。
它的 新生代采用复制算法,老年代采用标记整理算法。

ParNew 收集器

ParNew 收集器是 Serial 收集器的多线程版本,除了使用多线程进行垃圾收集之外,其余行为(控制参数、收集算法、分配规则、回收策略等等)和 Serial 收集器完全一样。

除了支持多线程收集,ParNew 相对 Serial 似乎并没有太多改进的地方。但是它却是许多运行在 Server 模式下的虚拟机的首要选择,除了 Serial 收集器外,只有它能与 CMS 收集器(真正意义上的并发收集器,后面会介绍到)配合工作。ParNew单核状态下不如Serial,多核线程下才有优势。

新生代采用复制算法,老年代采用标记整理算法。

Parallel Scavenge 收集器

Parallel Scavenge 收集器是一个新生代收集器,也是采用复制算法+并行。听起来和ParNew差不多对不对,那么它有什么特别之处呢?

Parallel Scavenge 收集器关注点是吞吐量(CPU运行代码的时间与CPU总消耗时间的比值)。 而CMS 等垃圾收集器的关注点更多的是缩短用户线程的停顿时间(提高用户体验)。停顿时间越短就越适合和用户进行交互(响应速度快,可以优化用户体验),而高吞吐量则可以高效的利用CPU时间,尽快完成用户的计算任务。

Parallel Scavenge 收集器提供了很多参数供用户找到最合适的停顿时间或最大吞吐量,如果对于收集器运作不太了解的话,手工优化存在的话可以选择把内存管理优化交给虚拟机去完成也是一个不错的选择。

Serial Old 收集器

Serial 收集器的老年代版本,它同样是一个单线程收集器。它主要有两大用途:一种用途是在 JDK1.5 以及以前的版本中与 Parallel Scavenge 收集器搭配使用,另一种用途是作为 CMS 收集器的后备方案。

新生代采用复制算法,老年代采用标记整理算法。

Parallel Old 收集器

Parallel Scavenge 收集器的老年代版本。使用多线程和“标记-整理”算法。在注重吞吐量以及 CPU 资源的场合,都可以优先考虑 Parallel Scavenge 收集器和 Parallel Old 收集器。

CMS 收集器

CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。它非常重视服务的响应速度,以期给用户最好的体验。。

从名字中的Mark Sweep这两个词可以看出,CMS 收集器是一种 “标记-清除”算法实现的,它的运作过程相比于前面几种垃圾收集器来说更加复杂一些。整个过程分为四个步骤:

  • 初始标记 需要“Stop the world”,仅仅只是标记一下GC Roots 能直接关联到的对象,速度很快。

  • 并发标记 并发追溯标记,程序不会停顿。

  • 重新标记 需要“Stop the world”修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录

  • 并发清除 清理垃圾对象,程序不会停顿

CMS一款优秀的垃圾收集器,主要优点:并发收集、低停顿。但是它有下面三个明显的缺点:

  • 对 CPU 资源敏感;
  • 无法处理浮动垃圾;
  • 它使用的回收算法-“标记-清除”算法会导致收集结束时会有大量空间 碎片产生。

G1 收集器

G1 (Garbage-First) 是一款面向服务器的垃圾收集器,开发人员希望在未来可以换掉CMS收集器,它有如下特点

  • 并行与并发:G1 能充分利用 CPU、多核环境下的硬件优势,使用多个 CPU(CPU 或者 CPU 核心)来缩短 Stop-The-World 停顿时间。部分其他收集器原本需要停顿 Java 线程执行的 GC 动作,G1 收集器仍然可以通过并发的方式让 java 程序继续执行。
  • 分代收集:虽然 G1 可以不需要其他收集器配合就能独立管理整个 GC 堆,但是还是保留了分代的概念。
  • 空间整合:与 CMS 的“标记--清理”算法不同,G1 从整体来看是基于“标记整理”算法实现的收集器;从局部上来看是基于“复制”算法实现的。这就意味着不会产生大量的内存碎片
  • 可预测的停顿:这是 G1 相对于 CMS 的另一个大优势,降低停顿时间是 G1 和 CMS 共同的关注点,但 G1 除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为 M 毫秒的时间片段内。

G1 收集器的运作大致分为以下几个步骤:

  • 初始标记
  • 并发标记
  • 最终标记
  • 筛选回收

G1收集器将整个Java堆内存划分为若干个内存大小相等的Region,年轻代和老年代不再物理隔离,他们都是一部分Region的集合。

posted on 2019-06-17 21:26  将图南  阅读(1589)  评论(0编辑  收藏  举报