【JVM】GC(三):垃圾收集器

本文详细介绍了Java虚拟机中的垃圾收集器,包括串行收集器如Serial和SerialOld,强调吞吐量的ParallelScavenge和ParallelOld,以及并发收集器CMS和G1。CMS以最短停顿时间为目标,而G1则提供可预测的停顿时间模型。选择垃圾收集器需考虑应用场景和性能需求。

在前面两篇文章,我们分别介绍了垃圾回收判断和回收算法

本篇我们就来看看这些算法的具体实践者–垃圾收集器。

Java 虚拟机规范对垃圾收集器应该如何实现没有任何规定,因为没有所谓最好的垃圾收集器出现,更不会有万金油垃圾收集器,只能是根据具体的应用场景选择合适的垃圾收集器。

在这里插入图片描述

1.串行收集器

只能有一个垃圾回收线程执行,用户线程暂停。 适用于内存比较小的嵌入式设备 。

1.1 Serial(young)

Serial(串行)收集器收集器是最基本、历史最悠久的垃圾收集器了。大家看名字就知道这个收集器是一个单线程收集器了。它的 “单线程” 的意义不仅仅意味着它只会使用一条垃圾收集线程去完成垃圾收集工作,更重要的是它在进行垃圾收集工作的时候必须暂停其他所有的工作线程( “Stop The World” ),直到它收集结束。

新生代采用复制算法,老年代采用标记-整理算法。
在这里插入图片描述
虚拟机的设计者们当然知道Stop The World带来的不良用户体验,所以在后续的垃圾收集器设计中停顿时间在不断缩短(仍然还有停顿,寻找最优秀的垃圾收集器的过程仍然在继续)。

但是Serial收集器有没有优于其他垃圾收集器的地方呢?当然有,它简单而高效(与其他收集器的单线程相比)。Serial收集器由于没有线程交互的开销,自然可以获得很高的单线程收集效率。Serial收集器

对于运行在Client模式下的虚拟机来说是个不错的选择。

  • 特点
    • 单线程
    • Stop The World
    • 简单高效
  • 收集算法
    • 新生代:复制算法
    • 老年代:标记整理

1.2 Serial Old(old)

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

  • 特点:Serial的老年代版
  • jdk1.5及以前与Parallel Scavenge搭配,CMS后备方案

2.并行收集器

多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态。 适用于科学计算、后台处理等若交互场 景 。

2.1 ParNew(young)

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

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

在这里插入图片描述

  • 特点:Serial的多线程版本

2.2 Parallel Scavenge(young)

Parallel Scavenge 收集器类似于ParNew 收集器。

Parallel Scavenge 收集器关注点是吞吐量(高效率的利用CPU)。CMS 等垃圾收集器的关注点更多的是用户线程的停顿时间(提高用户体验)。所谓吞吐量就是CPU中用于运行用户代码的时间与CPU总消耗时间的比值。

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

新生代采用复制算法,老年代采用标记-整理算法。
在这里插入图片描述

  • 特点
    • Server模式下的默认收集器(内存大于2G,2个CPU时)
    • 注重吞吐量
  • 收集算法
    • 新生代:复制算法
    • 老年代:标记整理

3.3 Parallel Old(old)

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

  • 特点:Parallel Scavenge的老年代版本

3.并发收集器

用户线程和垃圾收集线程同时执行(但并不一定是并行的,可能是交替执行的),垃圾收集线程在执行的时候不会停顿用户线程的运行。 适用于相对时间有要求的场景,比如Web 。

并行和并发概念对比:

  • 并行(Parallel) :指多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态。
  • 并发(Concurrent):指用户线程与垃圾收集线程同时执行(但不一定是并行,可能会交替执行),用户程序在继续运行,而垃圾收集器运行在另一个CPU上。

3.1 CMS(old)

CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。它而非常符合在注重用户体验的应用上使用。

CMS(Concurrent Mark Sweep)收集器是HotSpot虚拟机第一款真正意义上的并发收集器,它第一次实现了让垃圾收集线程与用户线程(基本上)同时工作。

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

  1. 初始标记(CMS initial mark): 暂停所有的其他线程,并记录下直接与root相连的对象,速度很快
  2. 并发标记(CMS concurrent mark): 同时开启GC和用户线程,用一个闭包结构去记录可达对象。但在这个阶段结束,这个闭包结构并不能保证包含当前所有的可达对象。因为用户线程可能会不断的更新引用域,所以GC线程无法保证可达性分析的实时性。所以这个算法里会跟踪记录这些发生引用更新的地方。
  3. 重新标记(CMS remark): 重新标记阶段就是为了修正并发标记期间因为用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段的时间稍长,远远比并发标记阶段时间短
  4. 并发清除(CMS concurrent sweep): 开启用户线程,同时GC线程开始对为标记的区域做清扫。

在这里插入图片描述

  • 特点
    • 优点
      • 以最短回收停顿为目标
      • 并发收集
    • 缺点
      • 对cpu资源敏感,会和服务抢夺资源
      • 无法处理浮动垃圾(在本次GC过程中产生的垃圾只能等到下次gc时处理)
      • 会产生大量的不连续空间碎片(标记-清除)
  • 收集算法
    • 新生代:复制算法
    • 老年代:标记清除

3.2 G1收集器

G1 (Garbage-First)是一款面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器. 以极高概率满足GC停顿时间要求的同时,还具备高吞吐量性能特征.
在这里插入图片描述
G1 将Java堆划分为多个大小相等的独立区域(Region),虽保留新生代和老年代的概念,但不再是物理隔阂了,它们都是(可以不连续)Region的集合。

PS:分配大对象时直接进Humongous区(专门存放短期巨型对象),不用直接进老年代,避免Full GC的大量开销)不会因为无法找到连续空间而提前触发下一次GC。

G1 特点

G1被视为JDK1.7中HotSpot虚拟机的一个重要进化特征。它具备以下特点:

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

G1 垃圾收集等级

G1 的垃圾收集有三个等级:

  • YoungGC:主要是回收Young区的垃圾
    • 新对象进入Eden区
    • 存活对象拷贝到Survivor区
    • 存活时间达到年龄阈值时,对象晋升到Old区
  • MixedGC:混合GC(不是FullGC)。回收所有的Young和部分Old(根据期望的GC停顿时间确定old区垃圾收集的优先顺序)。MixedGC必须满足下列两个条件:
    • -XX:InitiatingHeapOccupancyPercen:老年代占用空间超过整堆比IHOP阈值(默认45%),超过则执行混合收集
    • -XX:G1HeapWastePercent:堆废物百分比(默认5%)
  • FullGC:G1也是有FullGC的,不过会通过小的MixedGC尽量避免

G1内部,混合GC是非常重要的释放内存机制,它避免了G1出现Region没有可用的情况,否则就会触发Full GC事件。

CMS、Parallel、Serial GC都需要通过Full GC去压缩老年代并在这个过程中扫描整个老年代。G1的Full GC算法和Serial GC收集器完全一致。当一个Full GC发生时,整个Java堆执行一个完整的压缩,这样确保了最大的空余内存可用。G1的Full GC是一个单线程,它可能引起一个长时间的停顿时间,G1的设计目标是减少Full GC,满足应用性能目标。

G1 垃圾收集过程

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

  1. 初始标记:标记一下GC Roots能够关联的对象,并且修改TAMS的值,需要暂停用户线程
  2. 并发标记:从GC Roots进行可达性分析,找出存活的对象,与用户线程并发 执行
  3. 最终标记:修正在并发标记阶段因为用户程序的并发执行导致变动的数据,需暂停用户线程
  4. 筛选回收:对各个Region的回收价值和成本进行排序,根据用户所期望的GC停顿时间制定回收计划

在这里插入图片描述

如何实现筛选回收的呢?G1收集器在后台维护了一个优先列表,每次根据允许的收集时间,优先选择回收价值最大的Region(这也就是它的名字Garbage-First的由来)。

这种使用Region划分内存空间以及有优先级的区域回收方式,保证了GF收集器在有限时间内可以尽可能高的收集效率(把内存化整为零)。

G1特点小结

  • 将Java堆划分为多个大小相等的独立区域,新生代与老年代不再是物理隔阂。分配大对象时直接进Humongous区,专门存放短期巨型对象

  • 可预测的停顿:可建立预测停顿的时间模型,让使用者指定在M毫秒内完成垃圾收集。G1在后台维护了一个优先队列,每次根据允许收集的时间,优先选择回收价值最大的Region

  • 分代收集:G1可以独立管理整个GC堆,但是还是保留了分代的概念

  • 并发收集:同时满足高吞吐,低停顿

  • 收集方法:标记整理

4.怎么选择垃圾收集器?

  1. 优先调整堆的大小让服务器自己来选择
  2. 如果内存小于100m,使用串行收集器
  3. 如果是单核,并且没有停顿时间的要求,串行或JVM自己选择
  4. 如果允许停顿时间超过1秒,选择并行或者JVM自己选
  5. 如果响应时间最重要,并且不能超过1秒,使用并发收集器

官方推荐G1,性能高。JDK 7开始使用,JDK 8非常成熟,JDK 9默认的垃圾收集器,适用于新老生代。判断是否需要使用G1收集器?

  1. 50%以上的堆被存活对象占用
  2. 对象分配和晋升的速度变化非常大
  3. 垃圾回收时间比较长。

如何开启需要的垃圾收集器 ?

  1. 串行 -XX:+UseSerialGC -XX:+UseSerialOldGC
  2. 并行(吞吐量优先): -XX:+UseParallelGC -XX:+UseParallelOldGC
  3. 并发收集器(响应时间优先) -XX:+UseConcMarkSweepGC -XX:+UseG1GC
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值