Java 主流垃圾收集器

垃圾收集器概述

可以用一句非常形象的话来描述垃圾收集器

如果说收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现。

这里我们主要介绍基于分代收集理论设计的垃圾收集器

Serial 与 Serial Old 垃圾收集器

SerialSerial Old 垃圾收集器,都是单线程收集的垃圾收集器。
其中 Serial 用于收集新生代,使用的是 Appel 式复制算法;SerialOld 用于收集老年代,使用的是标记整理算法。
他们的特点也非常明显,即都是单线程执行 GC 工作。

Serial 与 Serial Old 垃圾收集器总结

SerialSerial Old 垃圾收集器优缺点如下:
优点:

  • 实现简单
  • 单线程收集效率较高
  • 资源消耗低

缺点:

  • 无法运用多核 CPU 多线程并行处理优势,导致整体工作效率低下
因为资源消耗低和单线程工作的特性,`Serial` 与 `Serial Old` 垃圾收集器适用于内存资源受限或者处理器核心数较少的场景中,适用于运行在客户端模式下的虚拟机。

ParNew 垃圾收集器

ParNew 垃圾收集器,是一个支持多线程并行收集的新生代垃圾收集器,本质上是 Serial 的多线程并行版本。

ParNew 垃圾收集器,一个非常重要的特性就是除了 Serial 垃圾收集器外,只有它能跟 CMS 垃圾收集器搭配工作

ParNew 使用的也是 Appel 式复制算法

Parallel Scavenge 垃圾收集器

Parallel Scanvenge 垃圾收集器,也是一个支持多线程并行收集的垃圾收集器。
Parallel Scanvenge 垃圾收集器关注的是吞吐量,所以也被称为吞吐量优先收集器,吞吐量计算公式为:
吞 吐 量 = 运 行 用 户 代 码 时 间 运 行 用 户 代 码 时 间 + 运 行 垃 圾 收 集 时 间 吞吐量=\frac{运行用户代码时间}{运行用户代码时间 + 运行垃圾收集时间} =+
运行用户代码时间 + 运行垃圾收集时间基本上约等于程序运行总时间,则由公式可以得出:

  • 运行垃圾收集时间程序运行总时间比值越小,则吞吐量越高
  • 运行垃圾收集时间程序运行总时间比值越大,则吞吐量越低

Parallel Scavenge 的吞吐量控制参数

Parallel Scavenge 提供了两个用于精准控制吞吐量的参数,分别是:

  • 期望的最大垃圾收集停顿时间:-XX:MaxGCPauseMills,单位是毫秒
    • 当设置了这个参数之后,收集器将尽力保证单次垃圾收集花费的时间不超过设定好的参数值
    • 需要与用户频繁交互的场景下,垃圾收集停顿时间越小,则用户对于应用程序停顿的感知越少,即用户体验越好
    • 此参数并不是越小越好,因为垃圾收集停顿时间的缩小是以牺牲吞吐量和新生代空间为代价来换取
  • 期望的最低吞吐量:-XX:GCTimeRatio
    • 当设置了这个参数后,收集器将会尽量保证吞吐量大于等于设定好的参数
    • 默认值是 99,即收集器将会尽量保证吞吐量大于等于 99%

Parallel Scavenge 的自适应调节策略

Parallel Scavenge 提供了一个自适应调节策略开关参数:-XX+UseAdaptiveSizePolicy
当开启这个参数后,我们就不需要再手工指定新生代大小EdenSurvivor 大小比例等细节参数了。虚拟机将会根据当前应用的运行情况和系统资源,动态调整这些参数来使垃圾收集的性能最优,让程序获得最高的吞吐量。
注意,设置这个参数同时也可以设置期望的最大垃圾收集停顿时间期望的最低吞吐量参数,来给虚拟机设定一个优化目标。

Parallel Scavenge 垃圾收集器总结

Parallel Scavenge 是一款支持多线程并行收集的垃圾收集器,可以通过参数来精准控制吞吐量;也可以开启自适应调节策略,将虚拟机的一些运行细节参数交给虚拟机自行动态设置。
Parallel Scavenge 适用于计算密集型场景。

ParNew 和 Parallel Scavenge 垃圾收集器的对比

ParNewParallel Scavenge 都是支持多线程并行收集的垃圾收集器,它们之间的区别主要有以下几点:

  • Parallel Scavenge 可以精确控制吞吐量
  • Parallel Scavenge 可以开启自适应调节策略
  • ParNew 可以与 CMS 搭配使用,Parallel Scavenge 不能

Parallel Old 垃圾收集器

Parallel Old 垃圾收集器,是一个支持多线程并行收集的老年代垃圾收集器。
Parallel Old 基于标记-整理算法实现,主要用于与 Parallel Scavenge 搭配使用。
JDK 8 默认的垃圾收集器组合就是 Parallel ScavengeParallel Old

CMS 垃圾收集器

CMS 垃圾收集器,全称 Concurrent Mark Sweep 垃圾收集器,是一种支持多线程并行清理,且能与用户线程并发执行的垃圾收集器。从字面意思来看,可以理解为并发标记清除垃圾收集器,也被称为并发低停顿收集器
CMS 基于标记-清除算法实现。

CMS 垃圾收集器的收集过程

CMS 垃圾收集器的收集过程如下:

  • 初始标记:执行 STW,并标记下 GC Root 能直接引用的对象。这个过程相当迅速
  • 并发标记:退出 STW,多个 GC 线程从初始标记标记过的对象开始遍历对象的引用关系图
    • 此过程多个 GC 线程与用户线程一起运行,且耗时较长
  • 重新标记:执行 STW,并修正并发标记期间标记产生变动的那一部分对象的标记记录(采用增量更新的方式解决)
    • 并发标记过程中,GC 线程与用户线程同时在运行,所以很可能会导致一些已经标记过的对象的存活状态发生改变,故而需要 STW 来修正这些变动的标记
  • 并发清理:退出 STWGC 线程开始清理标记阶段判定为死亡的对象
    • 此过程多个 GC 线程与用户线程一起运行
    • 清理完成后不需要移动存活对象,即没有整理内存碎片
  • 并发重置:重置本次 GC 过程中的标记数据
    • 此过程 GC 线程与用户线程一起运行

整个过程如下所示:

CMS 的缺点

CMS 的优点非常明显,即并发收集低停顿。与此同时,也有几个明显的缺点

处理器资源敏感

CMS 垃圾收集器在与用户线程并发执行的阶段中,虽然不会暂停用户线程,但是由于 GC 线程的执行本身就需要一部分系统资源,所以会抢占用户线程的系统资源,导致用户线程的执行变慢,从而降低总吞吐量。
这种情况在系统资源充足,处理器核心数比较多的场景中并不是很明显(但多少还是会有影响),但是一旦处理器核心数较少时(少于 4 个),(GC 线程对资源的占用导致应用程序变慢的情况)可能就会变得比较明显。

Concurrent Mode Failure

由收集过程我们可以知道,在并发标记并发清理阶段,用户线程和 GC 线程是并发执行的。
那么在这两个阶段的实际的执行过程中,由于用户线程在同时执行,即用户线程在正常地调用、退出方法创建和销毁栈帧),声明对象,或者修改引用关系,那么很可能出现再次触发 GC 的情况(即用户线程的这些操作又导致可用空闲不足或者触发了其他垃圾回收的条件)。
这种情况就是 Concurrent Mode Failure,在这种情况发生后,会立即 STW,使用 Serial Old 垃圾收集器来回收。这不是个好消息,因为 Seial Old 的收集效率比较低下。
即当出现 Concurrent Mode Failure 时,JVM 会使用 Serial Old 垃圾收集器进行回收,本次的停顿时间将会变得很长。

浪费堆内存空间

由于在执行 GC 的过程中,还需要让用户线程继续执行,所以触发 GC 的条件不能像其他老年代垃圾收集器一样,一直到快满了才触发,而是需要预留一部分空间,让执行 GC 的过程中并发执行的用户线程使用。
这个预留空间的大小(一个比例)通常可以配置,默认是 92。即触发 GC 的条件是老年代的空间使用了 92% ,剩余的 8% 将预留给执行 GC 过程中并发执行的用户线程使用。
这个空间并不是越小越好,当执行 GC 过程中,这个预留的空间不够用而导致触发一次新的 GC 时,将会出现一次 Concurrent Mode Failure
所以,这里就需要有一个取舍:

  • 如果这个预留空间设置得比较大,那么将会浪费内存空间
  • 如果这个预留空间设置得比较小,那么可能会导致频繁出现 Concurrent Mode Faliure,从而拉低吞吐量

没有很好地处理内存碎片

CMS 是一款基于标记-清除算法实现的垃圾收集器,在拥有标记-清除算法带来的优点的同时,不例外地,它也存在算法带来的缺点——没有整理内存碎片
当内存碎片过多时,就会给大对象的内存分配带来麻烦:老年代还剩余很多内存空间,但是却找不到一块足够大的连续的内存空间,当出现这种情况时,虚拟机不得不发起一次 Full GC 来尝试获得一块足够大的连续的内存空间。
CMS 为解决这个问题,提供了两个解决方案:

  • 开启 -XX:+UseCMSCompactAtFullCollection 参数,让虚拟机在执行 Full GC 后执行一次碎片整理,开启后基本等同于标记-整理算法
    • 由于碎片整理需要移动存活的对象,所以这个过程是需要 STW 的,这会导致停顿时间变长,从而降低吞吐量
  • 设置 -XX:CMSFullGCsBeforeCompaction 参数,即进行多少次 Full GC 后执行一次内存碎片整理
    • 例如当此参数的值设置成 3 的含义代表着,进行了 3Full GC 后在下次(即第 4 次) Full GC 执行前,先执行一次内存碎片整理后,再执行 Full GC
    • 默认值为 0,即每次进行 Full GC 之前都要先整理一遍内存碎片

无法处理浮动垃圾

如果在两个与用户线程并发执行的过程中,又有新的垃圾对象产生,那么这部分对象将变成浮动垃圾,则本次清理过程并不能处理掉这些对象。
很多时候,浮动垃圾带来了执行过程中的不确定性,例如很可能因为产生的浮动垃圾足够多而重新触发了一次新的 GC 过程,这就会引起 Concurrent Mode Failure,从而使吞吐量下降

G1 垃圾收集器

G1,即 Garbage First 垃圾收集器,它是一款面向服务端应用的垃圾收集器。在系统资源充足(大容量内存和 CPU 核心数较多)的情况下,可以同时满足低停顿与高吞吐量的要求。

G1 垃圾收集器管理下的堆内存布局

G1 将堆划分成了多个大小相等的独立区域(Region),每个区域都可以根据当前内存分配需要而扮演不同的角色,例如扮演 Eden 空间,扮演 Survivor 空间,或者扮演老年代空间。
G1 可以根据每个区域当前扮演的角色不同,采用通过不同的策略进行管理。

区域的主要特性

  • 区域最多为 2048
  • 每个区域的大小取值范围为 1~32MB,且必须为 2 的幂次方,可以通过参数 -XX:G1HeapRegionSize 来指定
  • 区域有四种角色:
    • Eden:表示这个区域当前是 Eden 空间的一部分
    • Survivor:表示这个区域当前是 Survivor 空间的一部分
    • Old:表示这个区域当前是老年代空间的一部分
    • Humongous:表示当前这个区域存储的是一个大对象(部分或全部,即一个大对象可能会存放在一个,或连续的几个 Humongous 区域中)
      • 当一个对象的大小超过了区域大小的 50%,那么就会被判定为大对象
  • 新生代、老年代的概念依然存在,但是大小不再固定,空间也并不连续
    • 新生代是由当前堆中所有扮演 EdenSurvivor 角色的区域的合集,这些区域并不一定连续,总大小也不固定
    • 老年代是由当前堆中所有扮演老年代角色的区域的合集

G1 垃圾收集的过程

  • 初始标记:进行 STW,并标记 GC Root 能直接引用的对象
  • 并发标记:结束 STW,多个 GC 线程与用户线程并发执行,遍历上一步标记的对象的引用关系图,判定对象的存活状态
  • 最终标记:进行 STW,使用原始快照SATB)的方法处理上一步中对象引用关系存在变化的对象
  • 筛选回收:进行 STW,对需要进行回收的 Region 进行回收价值和成本的排序,并结合用户期望的停顿时间来制定回收计划
    • 最终制定的回收计划中,需要回收的 Region 当前所扮演的角色可能是不同的,但是一定是当前最值得回收的一些区域
    • 回收时,将会把这些 Region 中存活的对象全部复制到空的 Region 中,再清空旧 Region 的空间
    • 用户期望的停顿时间对于决定最后要回收的回收集是相当重要的,如果这个值越小,那么可能最后要回收的回收集的数量也越少,档次回收效率也会变低

G1 垃圾收集动作分类

Young GC/Minor GC

Young GC(或者说 Minor GC),回收范围为年轻代。当现有的所有的 Eden 区放满了不会立马触发,而是会估算现有的 Eden 区回收需要的时间,估算出来的值与用户指定的最大停顿时间进行对比:

  • 如果估算出来的回收现有 Eden 区所需的时间远小于用户指定的最大停顿时间,那么将不会执行 Young GC,而是会增加年轻代的 Region
  • 如果估算出来的回收现有 Eden 区所需的时间接近于用户指定的最大停顿时间,那么将执行 Young GC

Mixed GC

Mixed GC,回收范围为所有区域,当老年代的区域占有率达到设定的参数值时触发。
使用复制算法,当执行拷贝发现已经没有足够可用的空 Region 区域时,将会触发一次 Full GC

Full GC

Full GC,回收范围为所有区域。当 Mixed GC 执行中发现没有足够可用的空 Region 区域时触发。
Full GC 将会进行 STW,然后采用单线程进行标记清除压缩整理,以便释放出新的空 Region

G1 垃圾收集器总结

G1 垃圾收集器的主要特点如下:

  • 独特的堆内存划分方式
  • 良好的内存碎片整理机制:G1 整体是基于标记-整理算法来实现的,但是从局部来看是基于复制算法来看实现的
  • 可预测的停顿时间模型:用户通过指定 -XX:MaxGCPauseMills确切地指定每次 GC 的最大停顿时间
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值