三、垃圾回收器介绍

【目录】 【上一篇:垃圾回收相关算法】 【下一篇:Class 文件结构】

三、垃圾回收器

垃圾收集器没有在规范中过多的进行规定,可以由不同的厂商、不同版本的 JVM来实现

💡 查看 JVM 使用垃圾回收器的方式:
①、使用:-XX:+PrintCommandLinmeFlags 命令行相关参数
②、使用指令:jinfo -flag [ 相关垃圾回收器参数 ] [ 进程ID ]

1、GC 分类与性能指标

评估 GC 性能的指标:

  • 吞吐量:运行用户代码的时间占总运行时间的比例( = 运行用户代码时间 / (运行用户代码时间 + 垃圾收集时间))
  • 垃圾收集开销:垃圾收集所用时间与总运行时间的比例(吞吐量的补数,与吞吐量相加等于1,b / a + b)
  • 暂停时间:执行垃圾收集时,程序的工作线程被暂停的时间(STW)
  • 收集频率:相对于应用程序的执行,收集操作发生的频率
  • 内存占用:Java 堆区所占的内存大小
  • 快速:一个对象从诞生到被回收所经历的时间。

吞吐量、暂停时间、内存占用 这三项共同构成一个”不可能三角“。这三项里,最重要的是暂停时间,吞吐量其次,最后是内存(毕竟现在内存越来越大了)。现在标准:在最大吞吐量优先的情况下,尽量降低停顿时间。

2、Serial 回收器:串行回收

  • Serial 收集器是最基本的、历史最悠久的垃圾收集器。JDK1.3 之前回收新生代唯一的选择;
  • Serial 收集器作为 HotSpot 中 Client 模式下的默认新生代垃圾收集器;
  • Serial 收集器是单线程的、采用复制算法、串行回收和 “STW” 机制方式的执行内存回收;
  • 除了年轻代之外,Serial 收集器还提供于执行老年代垃圾收集器的 Serial Old 收集器。Serial Old 收集器同样也采用了串行回收和 “STW” 机制,只不过内存回收算法使用的是标记 - 压缩算法。
    • Serial Old 是运行在 Client 模式下默认的老年代的垃圾回收器;
    • Serial Old 是 Server 模式下主要有两个用途:
      • ①、与新生代的 Parallel Scavenge 配合使用;
      • ②、作为老年代 CMS 收集器的后备垃圾收集方案。

优势简单高效(与其它收集器的单线程来说),对于限定单个 CPU 的环境来说,Serial 收集器由于没有线程交互的开销,专心做垃圾收集,自然可以获得最高的单线程收集效率。

命令: -XX:UseSerialGC

3、ParNew 回收器:并行回收

  • ParNew(Parallel New 的缩写)采用的是多线程并行回收的方式执行垃圾收集的,除了这一点之外,其他方面几乎与 Serial 收集器一样,在年轻代中同样也是采用的复制算法、“STW” 机制。

命令: -XX:UseParNewGC

💡 该收集器在 JDK9 版本中已经被淘汰

4、Parallel Scavenge 回收器:并行回收 - 吞吐量优先

  • Parallel 采用的是多线程并行回收、复制算法和 “STW” 方式的垃圾回收器,其内部的自适应调节策略可以使垃圾回收达到一个可控制的吞吐量
  • JDK8 默认使用 Parallel 和 Parallel Old 收集器(JDK9 默认使用的是 G1)
  • 参数配置
    • -xx:+UseParallelGC:使用 Parallel 为新生代收集器,Parallel Old 为老年代收集器
    • -xx:+UseParallelOldGC:使用 Parallel 为新生代收集器,Parallel Old 为老年代收集器
    • -xx:ParallelGCThreads:设置年轻代并行收集器的线程数。一般的,最好与 CPU 数量相同,以避免过多的线程影响垃圾收集性能
      • 默认情况下,当 CPU 数量小于 8 ,ParallelGCThreads 的值等于 CPU 的数量
      • 当 CPU 数量大于 8,ParallelGCThreads 的值等于 3 + [5 * CPU数 ] / 8
    • -xx:MaxGCPauseMillis:设置垃圾收集器最大停顿时间
    • -xx:GCTimeRatio:垃圾收集时间占总时间的比例(默认值是 99)
    • -xx:+UseAdaptiveSizePolicy:设置 Parallel Scavenge 收集器具有自适应调节策略
      • 在这种模式下,年轻代的大小、Eden 和 Survivor 的比例、晋升老年代的对象年龄等参数会自动调整,以达到在堆大小、吞吐量和停顿时间之间的平衡点;在手动调整比较困难的场合,可以直接使用该自适应机制

5、CMS 回收器:低延迟

在 JDK1.5 时期,HotSpot 推出了一款在强交互应用中几乎可以认为有划时代意义的垃圾收集器:CMS(Concurrent - Mark - Sweep)收集器。这款收集器是 HotSpot 虚拟机中第一款真正意义上的并发收集器,第一次实现了让垃圾收集线程与用户线程同时工作。

💡 JDK9 中,该垃圾收集器表标记为 deprecate(过时);JDK14 中,该垃圾收集器被删除

CMS 收集器的关注点是尽可能缩短垃圾收集时用户线程的停顿时间,停顿时间越短(低延迟)就越适合与用户交互的程序;
>> 目前很大一部分的 Java 应用集中在互联网站或者 B/S 系统的服务端上,这类应用尤其重视服务的响应速度,希望系统挺短时间最短,以给用户带来较好的体验。

CMS 的垃圾收集算法采用 标记 - 清除 算法,并且也会 “STW”

工作原理

CMS 整个过程比之前的收集器都要复杂,整个步骤分为 4 步:初始标记、并发标记、重新标记、并发清理

  • 初始标记:在这个阶段中,垃圾收集线程会暂停用户线程,发生 STW ,这个阶段的主要任务仅仅只是标记出 GC Roots 能直接关联到的对象,一旦标记完之后就会恢复之前被暂停的用户线程,由于关联对象少,所有这里的执行速度非常快,用户线程的暂停时间也非常的短;
  • 并发标记:从 GC Roots 的直接关联对象开始遍历整个对象图的过程,这个过程耗时较长,但是不需要暂停用户线程,可以并发进行;
  • 重新标记:由于在并发标记阶段中,程序的工作线程会和垃圾回收线程一起运行,因此为了修正并发标记期间,因用户程序继续运行而导致标记产生变动的那部分对象的标记记录,这个阶段会导致用户线程暂停;
  • 并发清除:此阶段清理删除标记阶段判断出的已死亡对象,释放内存空间;由于不需要旭东存活对象,所以这个阶段也是可以与用户线程同时并发的(没有压缩算法,采用的是空闲列表算法)

💡 由于在垃圾收集阶段用户线程没有中断,所以在 CMS回收过程中,还应该确保用户线程有足够的内存可以使用。因此,CMS 收集器不是等老年代内存几乎填满了才执行,而是当堆内存使用率达到某一阈值时,就开始进行清理回收,以确保应用程序在垃圾收集期间还有足够的内存支持应用线程执行。要是 CMS 运行期间预留的内存无法满足应用线程所需,就会出现一次 “Concurrent Mode Failure” 失败,这时虚拟机将启动后备方案:临时启动 Serial Old 收集器来重新进行老年代的垃圾收集,这样停顿时间就更长了。

CMS 优点:并发收集、低延迟

CMS 缺点

①、会产生内存碎片:因为采用的是【标记 - 清除】算法,所以回收之后会产生空闲碎片,当空闲碎片不足以分配大对象时,会触发 Full GC

②、CMS 收集器对 CPU 资源非常敏感:在并发阶段,它虽然不会导致用户线程停顿,但是会 因为占用了一部分线程而导致用户线程变慢,总吞吐量会降低

③、CMS 收集器无法处理浮动垃圾:在并发标记阶段由于程序的工作线程和垃圾线程是同时运行的,那么在并发标记阶段如果产生新的垃圾对象,CMS 将无法对这些垃圾对象进行标记,从而导致不能及时回收,只能在下一次执行 GC 时再回收这些垃圾对象。

参数设置

  • -xx:+UseConcMarkSweepGC:手动指定使用 CMS 垃圾收集器
    • 开启该参数之后,会自动将 xx:+UseParNewGC 打开。即:ParNew(Young区用)+ CMS(Old区用)+ Serial Old(Old区备用)的组合
  • -xx:CMSInitiatingOccupanyFraction:设置堆内存使用率的阈值,一旦达到该阈值,便开始垃圾回收
    • jdk5 之前默认是 68,即当老年代的空间使用率达到 68% 时,就开始垃圾回收;JDK6 及之后默认是 92%。(通过该参数值可以有效降低 Full GC 的执行次数)
  • -xx:+UseCMSCompactAtFullCollection:用于指定在执行完 Full GC 之后,是否进行压缩整理
  • -xx:+CMSFullGCsBeforeCompaction:设置在执行多少次 Full GC 后对内存空间进行压缩整理(与 UseCMSCompactAtFullCollection 参数是一套)
  • -xx:+ParallelCMSThreads:设置 CMS的线程数量
    • CMS 默认启动的线程数是 (ParallelGCThreads + 3)/ 4

6、G1(Garbage First) 回收器:区域化分代式

G1 垃圾收集器是在 Java7 update 4 之后引入的一个新的垃圾回收器,官方给 G1 设定的目标是在延迟可控的情况下获得尽可能高的吞吐量;在 JDK1.7 版本正式启用,是 JDK9 及以后的默认垃圾收集器。

G1 是一款面向服务端应用的垃圾收集器,主要针对配备多核 CPU 及大容量内存的机器;

G1 是一个并行回收器,它把堆内存分割为很多不相关的区域(Region)(物理上不连续的);使用不同的 Region 来表示 Eden、幸存者 0 区、幸存者 1 区、老年区等;

G1 GC 有计划的避免在整个 Java 堆中进行全区域的垃圾收集;G1 跟踪各个 Region 里面的垃圾堆积的价值大小(回收所获得 的空间大小以及回收所需要时间的经验值),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的 Region。

G1 特点(优势)

并行与并发

  • 并行性:G1 在回收期间,可以有多个 GC 线程同时工作,有效利用多核计算能力;此时用户线程 STW;
  • 并发性:G1 拥有与应用程序交替执行的能力,部分工作可以和应用程序同时执行,因此,一般来说,不会在整个回收阶段发生完全性阻塞应用程序的情况。

分代收集

  • 从分代上看,G1 依然属于分代型垃圾回收器,它会区分年轻的和老年代,年轻代依然有 Eden 区和 Survivor 区。但从堆的结构上看,它不要求整个 Eden 区、年轻代或者老年代是连续的,也不再坚持固定大小和固定数量;
  • 将堆空间分为若干个区域(Region),这些区域中包含了逻辑上的年轻代和老年代

空间整合

  • G1 将内存划分为一个个的 Region,内存的回收是以 Region 作为基本单位的。Region之间是复制算法(回收这个 Region 时,把存活对象复制到另一个空闲的 Region),整体上可以看作是【标记 - 压缩】算法;

可预测的停顿时间模型

  • 由于分区的原因,G1 可以只选取部分区域进行回收,这样缩小了回收范围,对全局停顿情况的发生也能更好的控制;
  • G1 跟踪各个 Region 里面的垃圾堆积的价值大小(回收所获得的空间大小以及回收所需要时间的经验值),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的 Region,保证了 G1 收集器在有限的时间内可以获取尽可能高的收集效率。

缺点

  • 占用额外的内存空间;

参数

+XX:+UseG1GC:启动 G1 垃圾收集器

+XX:G1HeapRegionSize:设置每个 Region 的大小。值是 2 的幂,范围是 1MB 到 32 MB 之间。目标是根据最小的 Java 堆大小划分出约 2048 个区域。默认是堆内存的 1/2000

+XX:MaxGCPauseMillis::设置期望达到的最大 GC 停顿时间指标(JVM 会尽力实现,但不保证达到)。默认值是 200ms

+XX:ParallelGCThread:设置 STW 时 GC 线程数的值,最多设置为 8;

+XX:ConcGCThreads:设置并发标记的线程数量,将 n 设置为并行垃圾回收线程数(ParallelGCThreads)的 1/4 左右;

+XX:InitiatingHeapOccupancyPercent:设置触发并发 GC 周期的 Java 堆占用率阈值。超过 此值就触发 GC。默认是 45

6.1、分区 Region:化整为零

使用 G1 收集器时,它将整个 Java 堆划分为约 2048 个大小相同的独立 Region块,每个 Region 块大小根据堆空间的实际大小而定,整个被控制在 1MB 到 32MB 之间,且为 2 的 N 次幂,即 1MB、2MB、4MB、8MB、16MB、32MB。可以通过 -XX:G1HeapRegionSize 设定。所有的 Region 大小相同,且在 JVM 生命周期内不会被改变。虽然还保留有新生代和老年代的概念,但新生代和老年代不再是物理隔离的,它们都是一部分 Region 的集合,通过 Region 的动态分配方式实现逻辑上的连续。

一个 Region 可能属于 Eden、Survivor 或者 Old 区域,但是一个 Region 只能是一个角色(可以变换角色)。在 G1 垃圾收集器中还增加了一个新的内存区域,叫做 Humongous 内存区域,主要用于存储大对象,如果超过 1.5 个 Region,就放到 H 区,如下图所示:

💡 为什么要设置 Humongous ?
对于堆中的大对象,之前是默认分配到老年代,但如果它是一个短期存在的大对象,就会对垃圾收集器造成负面影响。为了解决这个问题,H 区应运而生,它专门用来存放大对象,如果一个 Region 存不下,会寻找连续的 H 区来存储。为了能找到连续的 H 区,有时候不得不启动 Full GC。G1 的大多数行为都把 H 区作为老年代的一部分来处理。

Region 的内存划分采用的是【指针碰撞法】
在这里插入图片描述

6.2、G1 垃圾回收过程

在这里插入图片描述

  1. 应用程序分配内存,当年轻代的 Eden 区用尽时开始年轻代回收过程;G1 年轻代收集阶段是一个 并行独占式(会触发 STW)收集器。在年轻代回收期,G1 GC 暂停所有应用程序线程,启动多线程执行年轻代回收。然后从年轻代区间移动存活对象到 Survivor 区或 Old 区。
  2. 当堆内存使用达到一定阈值(默认45%,+XX:InitiatingHeapOccupancyPercent)时,开始老年代并发标记过程;
  3. 标记完成马上开始混合回收过程。对于一个混合回收期,G1 GC 从老年区间移动存活对象到空闲区间,这些空闲区间成为新的老年代的一部分。和年轻代不同,老年代的 G1 回收器和其他 GC 不同,G1 的老年代回收器不需要整个老年代被回收,一次只需要扫描/回收一小部分老年代的 Region 就可以了。(混合回收是老年代 Region 和 年轻代 Region 一起执行回收的)

G1 回收器垃圾回收过程:Remembered Set(记忆集与写屏障)

  • 一个对象被不同区域引用问题:
    • 一个 Region 不可能是孤立的,一个 Region 中的对象可能被其他任意 Region 中对象引用,那么判断对象是否存活,是否需要扫描整个堆空间才能保证准确性?如果真全局扫描,会严重影响 Minor GC 的效率
  • 解决办法:无论是 G1 还是其他分代收集器,JVM 都是使用 Remembered Set 来避免全局扫描:
    • 每个 Region 都有一个对应的 Remembered Set,每次 Reference 类型数据写操作时,都会产生一个 Write Barrier(写屏障)来暂停操作;
    • 然后检查将要写入的引用指向的对象是否和该 Reference 类型数据在不在同一个 Region 中
    • 如果不同,通过 CardTable 把相关的引用信息记录到引用指向对象的所在 Region 对应的 Remembered Set 中;
    • 当进行垃圾收集时,在 GC 根节点的枚举范围加入 Remembered Set,就可以保证不进行全局扫描,也不会有遗漏。
      在这里插入图片描述

7、垃圾回收器总结

如果想要最小化的使用内存和并行开销:请选 Serial GC;

如果想要最大化应用程序的吞吐量:请选 Parallel GC;

如果想要最低延迟:请选 CMS GC;

垃圾收集器分类作用位置使用算法特点适用场景起止版本
Serial单线程串行运行新生代复制算法响应速度优先适用于单 CPU 环境下的 Client 模式起于 jdk1.3
Serial Old单线程串行运行老年代标记 - 压缩算法响应速度优先适用于单 CPU 环境下的 Client 模式起于 jdk1.3
ParNew多线程并行运行新生代复制算法响应速度优先适用于多 CPU 环境下的 Server 模式,与 CMS 配合使用止于 jdk9
Parallel多线程并行运行新生代复制算法吞吐量优先适用于后台运算而不需要太多交互的场景jdk8 默认垃圾收集器
parallel Old多线程并行运行老年代标记 - 压缩算法吞吐量优先适用于后台运算而不需要太多交互的场景jdk8 默认垃圾收集器
CMS多线程并发运行老年代标记 - 清除算法响应速度优先适用于互联网或 B/S 业务起于 jdk1.5;止于 jdk14
G1多线程并发、并行运行新生代、老年代标记 - 压缩、复制算法响应速度优先面向服务端应用起于 jdk7;jdk9 默认垃圾收集器

7.1、怎么选择垃圾回收器

Java垃圾收集器的配置对于JVM优化来说是一个很重要的选择,选择合适的垃圾收集器可以让JVM的性能有一个很大的提升。

怎么选择垃圾收集器?

  1. 优先调整堆的大小让JVM自适应完成。

  2. 如果内存小于100M,使用串行收集器

  3. 如果是单核、单机程序,并且没有停顿时间的要求,串行收集器

  4. 如果是多CPU、需要高吞吐量、允许停顿时间超过1秒,选择并行或者JVM自己选择

  5. 如果是多CPU、追求低停顿时间,需快速响应(比如延迟不能超过1秒,如互联网应用),使用并发收集器官方推荐G1,性能高。。

    现在互联网的项目,基本都是使用G1

最后需要明确一个观点:

  1. 没有最好的收集器,更没有万能的收集
  2. 调优永远是针对特定场景、特定需求,不存在一劳永逸的收集器

最终组合

Serial GC —》Serial Old GC;

Parallel GC —》Parallel Old GC;

G1

8、垃圾回收器的新发展

JDK10 之后,G1 中的 Full GC 已经实现从串行优化成并行的了;

8.1、ZGC

  • ZGC 与 Shenandoah 目标高度相似:在尽可能对吞吐量影响不大的前提下,实现在任意堆内存大小下都可以把垃圾收集的停顿时间限制在十毫秒以内的低延迟;
  • 在《深入理解 Java 虚拟机》一书中对 ZGC 的定义:ZGC 收集器是一款基于 Region 内存布局的、(暂时)不设分代的,使用了读屏障、染色指针和内存多重映射等技术来实现可并发的【标记 - 压缩】算法,以低延迟为首要目标的一款垃圾收集器。
  • ZGC 的工作过程可以分为 4 个阶段:并发标记 - 并发预备重分配 - 并发重分配 - 并发重映射;
  • ZGC 几乎在所有的地方并发的执行,除了初始标记是 STW 的。所以停顿黄司机几乎就耗费在初始标记上,这部分的实际时间是非常少的。

参数

XX:+UnlockExperimentalVMOptions -XX:+UseZGC

【目录】 【上一篇:垃圾回收相关算法】 【下一篇:Class 文件结构】

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值