JVM 垃圾回收算法有哪些?CMS、G1、ParNew、Serial、Parallel 原理是什么?

开发 前端
垃圾回收算法是垃圾收集器的基础。JVM 中经典的算法包括标记-清除、复制和标记-整理,它们共同构成了分代垃圾回收策略的理论基础。

在 Java 的世界里,垃圾回收(Garbage Collection,GC)就像一位默默奉献的清洁工,负责清理那些不再被需要的对象。

没有 GC,我们的程序内存迟早会被耗尽。但 GC 的运作并非无代价,不合适的垃圾回收策略可能导致高延迟甚至性能瓶颈。

本章将带你深入理解 JVM 的垃圾回收机制,从垃圾回收算法到收集器设计,再到 GC 日志分析与调优实践。

通过对这些内容的掌握,您将能在高并发场景下为 JVM 选择最佳的垃圾回收管理方案。

垃圾回收重点关注的区域

在 Java 虚拟机中,垃圾回收机制主要关注于运行时数据区的 "堆空间" 中的数据,其次关注的是 "方法区" 中的数据。

从垃圾回收频率上讲,新生代、老年代、字符串常量池 和 元空间 都是垃圾回收的重点关注区域。

图片图片

垃圾回收算法

垃圾回收算法是垃圾收集器的基础。JVM 中经典的算法包括标记-清除、复制和标记-整理,它们共同构成了分代垃圾回收策略的理论基础。

标记-清除算法(Mark-Sweep)

算法原理

标记-清除是最早的垃圾回收算法之一,主要分为两个阶段:

  1. 标记阶段:从 GC Roots 出发,遍历所有可达对象并将其标记为存活对象。
  2. 清除阶段:回收未被标记的对象,释放其占用的内存。

图片图片

优点:实现简单,适合存活对象少的场景。

缺点:内存碎片化:未被回收的对象分布不连续,可能导致大对象无法分配;性能低下,需要完整遍历堆,耗时较长。

标记-清除算法通常用于老年代的垃圾回收,适合生命周期较长、存活率高的对象。

复制算法(Copying)

复制算法将堆分为两块相等的内存区域(From 和 To)。每次 GC 时,只扫描 From 区,将存活对象复制到 To 区,最后清空 From 区。

图片图片

优点:没有碎片化问题,内存分配效率高。

缺点:浪费内存:需要额外空间保存对象;不适合老年代,存活对象多时复制成本高。

复制算法通常用于新生代回收,新生代对象存活率低,复制成本较低。

标记-整理算法(Mark-Compact)

标记-整理(Mark-Compact)是标记-清除的改进版,标记阶段相同,但清除阶段会将存活对象移动到内存的一端,然后清理剩余空间。

图片图片

优点:消除了内存碎片。不需要额外空间。

缺点:对象移动成本较高。

标记-整理算法通常用于老年代回收,解决老年代内存碎片问题。

分代收集算法

上面所介绍的 标记清除算法、复制算法 和 标记整理算法,它们各自都具有自己独特的优势和特点,每种算法都有各自相适应的场景,没有一种算法可以完全替代另一种算法。

在这样的背景下 分代收集算法 孕育而生,由于每个对象的生命周期各不相同,有的对象可以长期存活,有的对象朝生夕灭。

因此,针对不同生命周期的对象,可以采取不同的回收方式来提高回收效率。

在一般情况下,市面上大多数 Java 虚拟机中的 GC 都采用分代收集,即将 堆空间 划分为 新生代 和 老年代,不同生命周期的对象放到不同的区域中进行存储,并且针对不同的区域采用不同的回收策略,这就是我们常说的 分代收集 (Generational Collection)。

垃圾收集器深入解析

JVM 中的垃圾收集器基于上述算法设计,针对不同场景优化性能。接下来,我们深入剖析常见收集器的实现原理和应用场景。

Serial 收集器

Serial 收集器是单线程收集器,采用复制算法回收新生代,标记-整理算法回收老年代。GC 时会 Stop-The-World(STW),暂停所有应用线程。

图片图片

特点

  • 实现简单,单线程操作效率高。。
  • 使用 复制算法 回收新生代,标记-整理算法 回收老年代。
  • 没有线程切换的开销。
  • GC 时应用完全暂停,延迟较高。

适用于单线程应用或内存占用较小的场景,如嵌入式设备和简单的命令行工具。

-XX:+UseSerialGC: 启用 Serial 新生代和 Serial Old 老年代回收器。

ParNew 回收器

arNew GC 是一款并行回收器,除了在进行垃圾回收时使用多线程并发执行外,其它方面几乎和 Serial GC 一致,包括 回收算法、Stop The World、对象分配规则 和 回收策略 等,因此常称 ParNew GC 为 Serial GC 的多线程版本。

不过 ParNew GC 属于新生代回收器,只能对新生代中的对象进行回收。

图片图片

◆ 优点:

① 支持并行回收: ParNew GC 支持多线程并行进行回收,可以利用多核 CPU 的优势,提高垃圾回收的效率;

② 回收效率高且停顿时间短: ParNew GC 是一个专门用于回收年轻代的垃圾垃圾回收器,使用的是复制算法,并且回收的空间比价小,所以回收效率高且停顿时间短;

◆ 缺点:

① 浪费部分内存空间: ParNew GC 使用的是复制算法,需要将内存空间拆成两份,每次只使用其中一份内存空间存储对象,所以比较浪费内存空间;

② 老年代垃圾回收效率低: 由于 ParNew GC 只用于年轻代垃圾回收,而不处理老年代垃圾回收,因此老年代的垃圾回收效率低下,容易导致 Full GC;

◆ ParNew 回收器常配置参数。

① -XX:+UseParNewGC: 启用新生代回收器 ParNew。

② -XX:ParallelGCThreads: 配置 GC 的线程数量,通常推荐该值和 CPU 核心数量保持一致。

Parallel 收集器

Parallel 收集器使用多线程并行回收新生代(复制算法)和老年代(标记-整理算法),以提高吞吐量。

图片图片

特点:

  • 多线程并行回收,适合多核 CPU。
  • 优化吞吐量,回收时应用暂停时间较长。
  • STW 时间较长,对低延迟场景不友好。

适用于吞吐量优先的后台任务,如大数据处理和批量计算。

Parallel 回收器常配置参数:

  • -XX:+UseParallelGC: 启用新生代回收器 Parallel Scavenge。
  • -XX:+UseParallelOldGC: 启用老年代回收器 Parallel Old。
  • -XX:ParallelGCThreads: 配置 GC 的线程数量,通常推荐该值和 CPU 核心数量保持一致。
  • -XX:MaxGCPauseMillis: 配置 GC 回收最大停顿时间,即 Stop The World 时间。
  • -XX:GCTimeRatio:配置 GC 回收时间占总时间的比例,用于衡量吞吐量的大小,取值范围为 0-100,默认值为 99,也就表示垃圾回收时间不超过 1%。

该参数与 -XX:MaxGCPauseMillis 参数有一定矛盾性,因为设置较短的停顿时间可能会导致更多的 GC 次数,从而增加总的 GC 时间,使得 GCTimeRatio 容易超过设定的比例。

  • -XX:+UseAdaptiveSizePolicy:配置 Parallel Scavenge 回收器具有自适应调节策略。
  • 在这种模式下,新生代 Eden 和 Survivor 的比例、晋升老年代的对象年龄等参数会被自动调整,期望在堆大小、吞吐量和停顿时间之间达到最佳平衡。
  • 在手动调优比较困难的场合,可以直接使用这种自适应的方式,仅指定虚拟机的最大堆、目标的吞吐量 (GCTimeRatio) 和停顿时间 (MaxGCPauseMillis),让虚拟机自己完成调优工作。

CMS 收集器

CMS 收集器使用并发回收方式,降低 STW 时间,这款回收器是 HotSpot 虚拟机中第一款真正意义上的并发回收器,它第一次实现了让垃圾收集线程与用户线程同时工作。

CMS GC 是一款老年代回收器,其主要回收目标就是老年代中的对象,使用的是标记清除算法。

老年代回收分为四个阶段:

  1. 初始标记:标记 GC Roots 可达的对象(STW)。
  2. 并发标记:并发扫描对象引用关系(无 STW)。
  3. 重新标记:处理并发期间新增的对象(STW)。
  4. 并发清除:清理无效对象(无 STW)。

图片图片

特点:

  • 并发回收,减少暂停时间。
  • 存在内存碎片化问题。
  • 并发时可能影响应用性能。

适用于响应时间敏感的场景,如 Web 应用和在线交易系统。

CMS 回收器可配置参数:

  • -XX:+UseConMarkSweepGC: 启用 CMS 老年代回收器。
  • -XX:ParallelcMSThreads: 设置 CMS GC 执行时的线程数量。
  • -XX:CMSInitiatingOccupanyFraction: 设置堆内存使用率的阈值,一旦达到该阈值,便开始进行回收。
  • -XX:+UseCMSCompactAtFullCollection: 设置是否在执行完 Full GC 前对内存空间进行压缩整理,以此避免内存碎片的产生。
  • -XX:CMSFullGCsBeforeCompaction: 设置在执行多少次 Full GC 后对内存空间进行压缩整理。

G1 收集器

G1 将堆划分为多个固定大小的 Region,每个 Region 都是连续的一段内存区域,大小在 1MB ~ 32MB 之间,且为 2 的 N 次幂,具体大小根据堆的实际大小而定。

并且每个 Region 扮演着不同的角色,比如可以是 Eden 区、Survivor 区、Old 区或 Humongous 区等,通过全局标记统计每个 Region 的垃圾量,优先回收垃圾最多的 Region,降低停顿时间。

图片图片

G1 回收器整体使用的是 标记-整理算法,局部使用的是 复制算法。

在 G1 中支持三种回收模式,分别为 Young GC、Mixed GC 和 Full GC:

  • Young GC: 对新生代中的 Region 进行回收。当新生代的空间不足时会触发 Young GC,回收新生代中的垃圾对象。
  • Mixed GC: 这是一种混合回收模式,不仅回收新生代中的 Region,还会回收部分老年代中的 Region。这种模式旨在减少 Full GC 的频率,通过定期回收部分老年代 Region 来降低整个堆的垃圾积累。
  • Full GC: 对整个堆空间的所有 Region 进行回收,包括新生代和老年代。Full GC 通常在 G1 无法通过 Mixed GC 清理足够的空间时触发,或者在某些特殊情况下触发 (如系调用 system.gc() 方法触发)。

G1 回收器优缺点

◆ 优点:

可预测的停顿时间: G1 GC 将堆空间分成多个大小相等的 Region (区域),这些 Region 可以作为 Eden 区、Survivor 区或 Old 区等。通过在多个 Region 中执行并发垃圾收集,G1 能够实现可预测的停顿时间,这意味着垃圾收集的停顿时间可以预测和控制;

高效的内存回收: G1 GC 使用增量式的垃圾收集算法,不仅在特定区域中进行垃圾回收,还在整个堆中进行。这种全局性的垃圾回收方式使得 G1 能够高效地回收内存,并减少垃圾收集的停顿时间;

优化的内存分配: G1 GC 使用了一种名为 Remembered Set 的数据结构,可以追踪对象之间的引用,从而在内存分配时避免扫描整个堆。这一特性可以显著减少内存分配的时间;

空间整理: G1 GC 通过对未使用的 Region 进行空间整理,将零散的空闲空间合并成更大的连续空间块,从而优化内存使用,提高堆的利用率;

◆ 缺点:

对硬件资源要求较高: G1 GC 需要在多核 CPU 和大内存环境下运行才能充分发挥其优势。如果在较小的硬件资源上运行,可能会导致运行缓慢或 OutOfMemoryError 等问题;

初始标记和最终标记的时间较长: G1 GC 的初始标记和最终标记阶段需要单线程执行,并且会暂停应用程序。在处理大型内存空间时,这两个阶段可能会导致较长的停顿时间;

混合收集过程可能会影响吞吐量: G1 GC 的混合收集过程涉及多次暂停应用程序,这可能会对应用程序的吞吐量产生一定的影响;

责任编辑:武晓燕 来源: 码哥跳动
相关推荐

2022-01-20 10:34:49

JVM垃圾回收算法

2023-12-07 12:21:04

GCJVM垃圾

2009-08-14 08:56:49

Java垃圾回收器G1

2021-08-10 07:00:02

Java8 G1垃圾回收器

2024-10-22 16:26:11

2009-07-24 09:41:45

Java 7 G1垃圾回收器

2020-06-01 20:08:47

垃圾G1回收器

2017-08-04 10:53:30

回收算法JVM垃圾回收器

2022-03-21 11:33:11

JVM垃圾回收器垃圾回收算法

2022-06-22 09:54:45

JVM垃圾回收Java

2021-11-05 15:23:20

JVM回收算法

2022-02-25 08:01:34

CMS

2009-12-25 16:15:31

JVM垃圾回收算法

2024-10-24 08:31:26

2009-06-02 10:18:43

Java垃圾回收器垃圾回收器Java垃圾回收

2021-06-30 08:45:02

内存管理面试

2022-06-10 07:13:29

JVM垃圾回收

2012-01-09 16:53:36

JavaJVM

2024-03-15 08:04:30

G1CMSJVM

2022-08-29 08:01:43

G1CMS回收器
点赞
收藏

51CTO技术栈公众号