面试提问:JVM 垃圾回收算法有哪些?CMS、G1、ParNew、Serial、Parallel 原理是什么?...

大家好,我是码哥,《Redis 高手心法》畅销书作者。

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

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

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

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

进入正文前,介绍下我的《Java 面试高手心法 58 讲》专栏内容涵盖 Java 基础、Java 高级进阶、Redis、MySQL、消息中间件、微服务架构设计等面试必考点、面试高频点。

丢掉你收藏的那些所谓的「面试宝典」,因为它们大多数深度不够,甚至内容还有错误,这也是为何每次面试你都回答不好的原因,你只会看完就忘,还浪费时间。

垃圾回收重点关注的区域

在 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 后对内存空间进行压缩整理。

4.2.4 G1 收集器

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

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

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

在 G1 中支持三种回收模式,分别为 Young GCMixed GCFull 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 的混合收集过程涉及多次暂停应用程序,这可能会对应用程序的吞吐量产生一定的影响;

总结

最后,也向大家介绍下我的新书《Redis 高手心法》。本书基于 Redis 7.0 版本,拟人故事化方式和诙谐幽默的言语与各路"神仙"对话。

通过本章的学习,我们讲解了垃圾回收算法、收集器实现及调优技巧。

不同场景下选择合适的 GC 策略和参数,是优化高并发应用性能的关键。

你们在面试中是否遇到过 GC 性能调优问题?欢迎在评论区分享你的经验和问题。

往期推荐

<>

什么是 JVM?JVM 为什么是开发者必须了解的核心技术?
<>

<>

Redis 7.0 深度探秘:List 数据结构原理与实战指南
<>

<>

面试官拷打:Redis 高可用篇章中面试最常见的 6 个问题!
<>

<>

重生之MySQL SQL 执行的 7 大关键步骤,解锁新技能
<>

<>

重生之从零设计 MySQL 架构
<>

参考资料

  1. http://www.mydlq.club/article/138/#51-serial-回收器
相关推荐
DC_BLOG2 分钟前
数据结构绪论
java·数据结构·算法
Wils0nEdwards1 小时前
Leetcode 加一
java·算法·leetcode
xuchaoxin13751 小时前
三角矩阵和对称阵的压缩存储
算法·矩阵
youk1101 小时前
STM32 HAL库之SDIO例程 Micro SD卡 - 1
算法
power-辰南1 小时前
机器学习支持向量机(SVM)算法
人工智能·python·算法·机器学习·支持向量机
空雲.2 小时前
牛客周赛71(字符串,状压dp)
数据结构·算法
橘子遇见BUG2 小时前
算法日记48 day 图论(拓扑排序,dijkstra)
算法·图论
s_m_c2 小时前
Any2Policy: Learning Visuomotor Policy with Any-Modality(类似AnyGPT)
人工智能·深度学习·算法·机器学习
南宫生2 小时前
力扣-图论-10【算法学习day.60】
java·学习·算法·leetcode·图论
手捧向日葵的话语2 小时前
图论 —— 求解最短路径(Dijkstra算法、Bellman-Ford算法、Floyd-Warshall算法)
数据结构·c++·算法·图论