JVM-04-垃圾收集器

JVM-04-垃圾收集器

前序

上一篇介绍了几种常用的垃圾回收算法,包括标记-清除,标记整理,复制等,这些算法我们可以看做是内存回收的理论方法,那么在Java虚拟机中,由谁来具体实现这些方法呢?

没错,就是本篇博客介绍的内容——垃圾收集器。

1. 垃圾收集器的种类

mark

由上图我们可以总结出几个结论:

  • 新生代垃圾收集器:Serial、ParNew、Parallel Scavenge;
  • 老年代垃圾收集器:Serial Old(MSC)、Parallel Old、CMS;
  • 整堆垃圾收集器:G1

垃圾收集器之间的连线表示可以搭配使用,有如下几种组合:

Serial/Serial Old、

Serial/CMS、

ParNew/Serial Old、

ParNew/CMS、

Parallel Scavenge/Serial Old、

Parallel Scavenge/Parallel Old、

G1;

  • 串行收集器 Serial:Serial、Serial Old

  • 并行收集器 Parallel:Parallel Scavenge、Parallel Old

  • 并发收集器:CMS、G1

下面我们将逐个进行介绍:

2. Serial收集器

mark

这是一个最基本,历史最悠久的垃圾收集器,是JDK1.3之前新生代唯一的垃圾收集器。

该收集器的特点:

  1. 作用于新生代

由上图也可看出,这是一个新生代垃圾收集器,采用的垃圾回收算法是复制算法。

  1. 单线程

工作时只会使用一个CPU或者一条收集线程去完成工作。

  1. 进行垃圾收集时,必须暂停所有工作线程

也就是说使用Serial收集器进行垃圾回收时,别的工作线程都暂停,系统这时候会有卡顿现象产生。

  1. 适用场景

Serial 收集器由于没有线程交互的开销,对于限定单个CPU的环境,可以获得最高的单线程收集效率。

一般在用户的桌面场景中,分配给虚拟机管理的内存一般来说都不会很大,收集几十兆或者一两百兆的新生代,定顿的时间可以控制在几十毫秒,只要不是频繁发生的,这点停顿是可以接受的。

所以Serial收集器对于运行在Client模式下的虚拟机是一种很好的选择。

3. ParNew收集器

mark

这个收集器其实就是Serial收集器的多线程版本。

也就是说其特点除了多线程,其余和Serial收集器一样,事实上,这两个收集器实现上也共用了很多代码。

  • 作用与新生代:一个新生代垃圾收集器,采用的垃圾回收算法是复制算法。
  • 多线程:弥补了Serial收集器单线程的缺陷。
  • 使用场景
    • 由于其多线程的特性,是 大多数运行在Server模式下的虚拟机首选的新生代垃圾收集器。
    • 另外需要说明的是可以和接下来要介绍的划时代垃圾收集器CMS(Concurrent Mark Sweep)配合使用,也是一个重要原因。

4. Parallel Scavenge收集器

前面介绍的垃圾收集器关注点是尽可能缩小垃圾收集时的用户线程停顿时间。而Parallel Scanvenge收集器是为了达到了一个可控制的吞吐量

1
2
3
4
5
6
7
  吞吐量 = 运行用户代码的时间 / (运行用户代码的时间+垃圾收集时间)

  可以用下面两个参数进行精确控制:

  -XX:MaxGCPauseMills 设置最大垃圾收集停顿时间

  -XX:GCTimeRatio 设置吞吐量大小
  • 作用于新生代

一个新生代垃圾收集器,采用的垃圾回收算法是复制算法。

  • 多线程

并行的多线程垃圾收集器

  • 吞吐量

这个收集器可以精确控制吞吐量

  • 使用场景

设置垃圾收集停顿时间短适合需要与用户快速交互的程序;

而设置高吞吐量可以最高效的利用CPU效率,尽快的完成程序的运算任务,主要适合在后台运算而不需要太多交互的任务。

5. Serial Old 收集器

mark

  • Serial Old 收集器是Serial收集器的老年代版本,特点如下
    • 作用于老年代
    • 单线程
    • 使用标记-整理算法
    • 进行垃圾收集时,必须暂停所有工作线程

6. Parallel Old 收集器

mark

  • Parallel Old 是 Parallel Scavenge收集器的老年代版本,使用多线程和 标记 - 整理算法
    • 作用于老年代
    • 多线程
    • 使用标记-整理算法

除了具有以上几个特点,比较关键的是能和新生代收集器 Parallel Scavenge 配置使用,获得吞吐量最大化的效果。

7. CMS 收集器

mark

CMS,全称是(Concurrent Mark Sweep),顾名思义是并发的,采用标记-清除算法。

另外这个收集器也叫做并发低延迟收集器(Concurrent Low Pause Collector)

这是一款跨时代的垃圾收集器,真正做到了垃圾收集线程和用户线程(基本上)同时工作。

和 Serial 收集器的 Stop The World(妈妈打扫房间的时候,你不能再将垃圾丢到地上) 相比,真正做到了妈妈一边打扫房间,你一边丢垃圾。

  • 作用与老年代
  • 多线程
  • 标记-清除算法

整个算法过程分为如下4步:

  1. 初始标记(CMS initial mark):仅仅只是 GC Roots能够直接关联到的对象,速度很快,需要停顿
  2. 并发标记:进行 GC Roots Tracing 的过程,它在整个回收过程中耗时最长,不需要停顿。
  3. 重新标记:为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,需要停顿。
  4. 并发清除:不需要停顿。

在整个过程中耗时最长的并发标记和并发清除过程中,收集器线程都可以与用户线程一起工作,不需要进行停顿。

缺点:

  • 对CPU资源敏感:因为在并发阶段,会占用一部分CPU资源,从而导致应用程序变慢,总吞吐量会降低。
  • 产生浮动垃圾:由于CMS并发清理阶段用户线程还在工作,这个时候产生的垃圾,CMS无法在本次收集中处理掉他们,只能留到下一次GC将其处理掉,这部分称为 浮动垃圾
  • 产生内存碎片:由于采用的是标记-清除,很明显,会有空间碎片产生。

8. G1 收集器

这是当前收集器技术发展的最前沿成果,可以实现在基本不牺牲吞吐量的前提下完成低停顿的内存回收,首发于JDK8中,是JDK9默认的垃圾回收器。

mark

G1(Garbage-First) : 它是一款面向服务端应用的垃圾收集器,在多 CPU 和大内存的场景下有很好的性能。HotSpot 开发团队赋予它的使命是未来可以替换掉 CMS 收集器。

堆分为新生代和老年代,其他收集器进行收集的范围是整个新生代或者老年代,而G1可以直接对新生代和老年代一起回收。

它与前面讲的 CMS 垃圾收集器相比,有两个显著的改进:

  • 采用 标记-整理 的回收算法:这样不会产生空间碎片

  • 可以精确控制停顿时间:能让使用者明确指定一个长度为M毫秒的时间片内,消耗在垃圾回收上的时间不超过 N 毫秒。

  • 作用于整个java堆:G1收集器不区分年轻代和老年代,是整堆垃圾收集器。

G1 把堆划分成多个大小相等的独立区域(Region),新生代和老年代不再物理隔离。

mark

  • 通过引入 Region 的概念,从而将原来的一整块内存空间划分成多个的小空间,使得每个小空间可以单独进行垃圾回收。

  • 这种划分方法带来了很大的灵活性,使得可预测的停顿时间模型成为可能。

  • 通过记录每个 Region 垃圾回收时间以及回收所获得的空间(这两个值是通过过去回收的经验获得),并维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的 Region。

  • 每一个Region都有一个Remembered Set。用来记录这个Region对象所引用对象所在的Region。

  • 通过Remembered Set,在做可达性分析的时候可以避免全堆扫描。

9. ZGC 收集器

  这是JDK11发布的一款垃圾收集器,是一个可扩展的低延迟垃圾收集器,有如下特性:

  ①、暂停时间不超过10毫秒

  ②、暂停时间不会随堆或实时设置大小而增加

  ③、处理堆范围从几百M到几TB。

10. 如何选择垃圾收集器?

官方文档:https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/collectors.html

这里我们翻译一下结论:

  • 除非有非常严格暂停时间的要求,否则就让JVM自己选择垃圾收集器
  • 并且可以优先调整堆大小的提高性能。
  • 如果还不满足,有以下四条建议
    • 如果应用程序内存小于100M,那么使用选项选择串行收集器-XX:+UseSerialGC
    • 如果应用程序将在单核处理器上运行,并且没有停顿时间的要求,选择串行-XX:+UseSerialGC或者 JVM 自己选
    • 如果允许停顿时间超过1秒,选择并行或 JVM 自己选
    • 如果响应时间比总吞吐量更重要,并且垃圾收集暂停必须保持短于大约1秒,则使用-XX:+UseConcMarkSweepGC或选择并发收集器-XX:+UseG1GC

11. 名词解释

并行

  • 指多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态。

并发

  • 指用户线程与垃圾收集器线程同时执行(但不一定是并行的,可能会交替执行)
  • 适用于对响应快速的场景,比如Web

停顿时间

  • 垃圾收集器做垃圾回收中断应用执行的时间

吞吐量

  • 吞吐量 = 运行用户代码的时间 / (运行用户代码的时间+垃圾收集时间)
打赏
  • 版权声明: 本博客所有文章除特别声明外,均采用 Apache License 2.0 许可协议。转载请注明出处!
  • © 2019-2022 Zhuuu
  • PV: UV:

请我喝杯咖啡吧~

支付宝
微信