JVM 垃圾收集器全解析:从 Serial 到 ZGC 的技术演进

在 JVM 的垃圾回收机制中,垃圾收集器是内存回收算法的具体实现者。从 JDK 1.0 的 Serial 收集器到 JDK 17 的 ZGC,垃圾收集器的技术演进始终围绕着三个核心目标:缩短停顿时间提高吞吐量支持更大堆内存。本文将系统剖析 7 种主流垃圾收集器的工作原理、优缺点及适用场景,为不同应用场景的 GC 参数配置提供实战指南。

一、垃圾收集器的分类:并行与并发的核心区别

在深入了解具体收集器之前,需要明确两个关键概念:

  • 并行(Parallel):指多条 GC 线程同时工作,此时用户线程处于暂停状态(Stop The World)。并行收集能提高 GC 效率,但会导致应用卡顿。
  • 并发(Concurrent):指 GC 线程与用户线程同时工作,用户线程无需暂停或仅短暂暂停。并发收集能减少停顿时间,但会增加 CPU 开销和内存占用。

所有垃圾收集器都基于分代收集理论设计(新生代 + 老年代),但不同收集器对新生代和老年代的回收策略存在显著差异。以下是 7 种收集器的关系图谱:

(示意图:垃圾收集器的分代与组合关系,如 Serial GC 对应 Serial Old GC,Parallel Scavenge 对应 Parallel Old GC 等)

二、新生代收集器:处理 “朝生夕死” 的对象

新生代对象的特点是存活时间短、存活率低(约 98% 的对象会在第一次 GC 中被回收),因此新生代收集器均采用复制算法(高效处理短生命周期对象)。

2.1 Serial 收集器:单线程的 “入门级” 实现

核心特性

  • 单线程执行 GC,收集时必须暂停所有用户线程(Stop The World);
  • 新生代采用复制算法,老年代默认搭配 Serial Old 收集器(标记 - 整理算法)。

工作流程

  1. 当 Eden 区满时,触发 Minor GC,Serial 收集器启动单线程;
  1. 暂停所有用户线程,标记新生代中的存活对象;
  1. 将存活对象复制到 Survivor 区,清空 Eden 区和原 Survivor 区;
  1. 恢复用户线程执行。

优缺点

  • 优点:实现简单,内存占用小,无线程交互开销,在单 CPU 环境下效率较高;
  • 缺点:GC 时用户线程暂停时间随堆内存增大而增加,无法利用多核 CPU 优势。

适用场景

  • 客户端应用(如桌面程序、工具类软件);
  • 堆内存较小(<1GB)的场景;
  • JVM 参数:-XX:+UseSerialGC(新生代 Serial + 老年代 Serial Old)。

2.2 Parallel Scavenge 收集器:吞吐量优先的并行实现

核心特性

  • 多线程并行执行 GC,仍会产生 Stop The World;
  • 新生代采用复制算法,老年代搭配 Parallel Old 收集器(标记 - 整理算法);
  • 目标是提高吞吐量(吞吐量 = 用户线程执行时间 /(用户线程时间 + GC 时间))。

关键参数

  • -XX:ParallelGCThreads=n:设置 GC 线程数(默认与 CPU 核心数相同);
  • -XX:MaxGCPauseMillis=n:设置最大 GC 停顿时间(默认 - 1,无限制,值越小吞吐量可能越低);
  • -XX:GCTimeRatio=n:设置吞吐量比例(默认 99,允许 GC 时间占比 = 1/(1+n))。

实战案例


# 配置Parallel Scavenge收集器,最大停顿时间100ms,吞吐量比例99(允许1%的GC时间)

java -XX:+UseParallelGC -XX:MaxGCPauseMillis=100 -XX:GCTimeRatio=99 -jar app.jar

优缺点

  • 优点:多线程并行收集,吞吐量高,适合计算密集型应用;
  • 缺点:停顿时间较长,不适用于响应时间敏感的场景(如 Web 服务)。

适用场景

  • 后台计算、数据分析等对吞吐量要求高,对响应时间不敏感的应用;
  • 多核 CPU、堆内存中等(1GB~10GB)的场景。

2.3 ParNew 收集器:与 CMS 配合的并行收集器

核心特性

  • 多线程并行执行 GC(与 Parallel Scavenge 类似),但不追求吞吐量优化;
  • 唯一能与 CMS 收集器(老年代)配合工作的新生代收集器。

与 Parallel Scavenge 的区别

  • ParNew 可通过-XX:ParallelGCThreads控制线程数,但无MaxGCPauseMillis和GCTimeRatio参数(不优化吞吐量);
  • ParNew 更注重与 CMS 的兼容性,而 Parallel Scavenge 专注于吞吐量。

适用场景

  • 需要搭配 CMS 收集器的场景(如 Web 应用);
  • JVM 参数:-XX:+UseParNewGC(新生代 ParNew + 老年代默认 CMS)。

三、老年代收集器:处理长生命周期对象

老年代对象的特点是存活时间长、存活率高,因此老年代收集器多采用标记 - 清除标记 - 整理算法(减少内存碎片,适合长生命周期对象)。

3.1 Serial Old 收集器:Serial 的老年代搭档

核心特性

  • 单线程执行 GC,基于标记 - 整理算法;
  • 主要作为 Serial 收集器和 CMS 收集器的 “应急方案”(如 CMS 发生 Concurrent Mode Failure 时)。

工作流程

  1. 当老年代内存不足时,触发 Major GC,Serial Old 启动单线程;
  1. 暂停所有用户线程,标记老年代中的存活对象;
  1. 将存活对象向内存一端移动,清理边界外的内存(消除碎片);
  1. 恢复用户线程执行。

优缺点

  • 优点:实现简单,内存占用小;
  • 缺点:单线程效率低,停顿时间长(尤其在大堆内存场景)。

适用场景

  • 客户端应用或小内存服务(堆内存 < 1GB);
  • 作为 CMS 收集器的后备方案(不推荐主动使用)。

3.2 Parallel Old 收集器:Parallel Scavenge 的老年代搭档

核心特性

  • 多线程并行执行 GC,基于标记 - 整理算法;
  • 与 Parallel Scavenge 组成 “吞吐量优先” 的全并行收集方案。

与 Serial Old 的区别

  • 多线程并行收集,适合多核 CPU;
  • 标记 - 整理阶段采用多线程协作,效率更高。

适用场景

  • 与 Parallel Scavenge 配合,用于追求高吞吐量的后台应用;
  • JVM 参数:-XX:+UseParallelOldGC(需与-XX:+UseParallelGC同时启用)。

3.3 CMS 收集器:追求最短停顿的并发收集器

CMS(Concurrent Mark Sweep)是 JDK 5 引入的首款并发收集器,其设计目标是最短化 GC 停顿时间,非常适合 Web 应用等对响应时间敏感的场景。

核心特性

  • 基于标记 - 清除算法,实现 GC 线程与用户线程的并发执行;
  • 老年代收集器,需搭配 ParNew 或 Serial 收集器作为新生代收集器。

工作流程(四阶段)

  1. 初始标记(Initial Mark)
    • 暂停用户线程,标记 GC Roots 直接关联的对象(如老年代中被新生代引用的对象);
    • 耗时极短(通常毫秒级)。
  1. 并发标记(Concurrent Mark)
    • 恢复用户线程,GC 线程与用户线程并发执行;
    • 遍历老年代,标记所有可达对象(从初始标记的对象出发);
    • 耗时最长(数秒级),但用户线程无需暂停。
  1. 重新标记(Remark)
    • 暂停用户线程,修正并发标记期间因用户线程操作导致的标记变动(如对象引用被修改);
    • 耗时短于初始标记(通常十毫秒级)。
  1. 并发清除(Concurrent Sweep)
    • 恢复用户线程,GC 线程与用户线程并发执行;
    • 清理被标记的对象,释放内存;
    • 无停顿,但会产生内存碎片。

关键参数

  • -XX:+UseConcMarkSweepGC:启用 CMS 收集器;
  • -XX:CMSInitiatingOccupancyFraction:老年代占用率阈值(默认 68%,达到该值触发 CMS);
  • -XX:+UseCMSCompactAtFullCollection:Full GC 后进行内存整理(解决碎片问题,默认启用);
  • -XX:CMSFullGCsBeforeCompaction:指定多少次 Full GC 后进行一次整理(默认 0,即每次都整理)。

实战案例


# 启用CMS收集器,老年代占用率达80%时触发GC,每3次Full GC后整理一次内存

java -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=80 -XX:CMSFullGCsBeforeCompaction=3 -jar app.jar

优缺点

  • 优点:并发收集,停顿时间短(毫秒级),响应速度快;
  • 缺点:
    • CPU 敏感(并发阶段占用部分 CPU 资源,可能导致应用吞吐量下降);
    • 内存碎片(标记 - 清除算法),可能触发频繁 Full GC;
    • 需要预留内存(并发阶段用户线程仍在分配内存,需避免 OOM)。

适用场景

  • 互联网应用、Web 服务等对响应时间要求高的场景;
  • 堆内存中等(2GB~16GB),CPU 核心数较多的服务器。

3.4 G1 收集器:跨代收集的 “全能选手”

G1(Garbage-First)是 JDK 7 引入的跨代收集器,打破了传统的 “新生代 + 老年代” 物理隔离,将堆内存划分为多个大小相等的独立区域(Region),每个 Region 既可作为 Eden 区、Survivor 区,也可作为老年代区。

核心特性

  • 基于 “区域化分代式” 设计,兼顾新生代和老年代回收;
  • 采用复制算法(Region 间复制存活对象)和标记 - 整理算法(Region 内整理);
  • 可预测的停顿时间(通过-XX:MaxGCPauseMillis指定目标停顿时间)。

工作流程(五阶段)

  1. 初始标记(Initial Mark)
    • 暂停用户线程,标记 GC Roots 直接关联的对象;
    • 耗时短,通常与 Minor GC 同步执行。
  1. 并发标记(Concurrent Mark)
    • 恢复用户线程,遍历所有 Region,标记可达对象;
    • 记录 Region 的存活对象比例(用于后续筛选)。
  1. 最终标记(Final Mark)
    • 暂停用户线程,修正并发标记期间的标记变动;
    • 采用 “快照 - Atlético” 算法,高效处理并发修改。
  1. 筛选回收(Live Data Counting and Evacuation)
    • 暂停用户线程,根据 Region 的存活对象比例和MaxGCPauseMillis,选择回收价值最高的 Region(Garbage-First 的由来);
    • 将选中 Region 的存活对象复制到空 Region,同时清理原 Region(消除碎片)。

关键参数

  • -XX:+UseG1GC:启用 G1 收集器;
  • -XX:MaxGCPauseMillis=n:目标最大停顿时间(默认 200ms);
  • -XX:G1HeapRegionSize=n:设置 Region 大小(1MB~32MB,需为 2 的幂次方);
  • -XX:G1NewSizePercent和-XX:G1MaxNewSizePercent:新生代占比的上下限(默认 5%~60%)。

实战案例


# 启用G1,目标停顿时间100ms,Region大小4MB

java -XX:+UseG1GC -XX:MaxGCPauseMillis=100 -XX:G1HeapRegionSize=4m -jar app.jar

优缺点

  • 优点:
    • 支持大堆内存(10GB~100GB),停顿时间可控;
    • 内存碎片少(Region 复制 + 整理);
    • 兼顾吞吐量和响应时间。
  • 缺点:
    • 内存占用高(需要维护 Region 的元数据);
    • 并发阶段 CPU 开销大。

适用场景

  • 堆内存较大(>10GB)的服务端应用(如电商平台、分布式服务);
  • 对停顿时间敏感且需要可预测性的场景。

3.5 ZGC:超低延迟的新一代收集器

ZGC(Z Garbage Collector)是 JDK 11 引入的实验性收集器,JDK 15 正式转正,设计目标是停顿时间不超过 10ms,支持 TB 级堆内存。

核心特性

  • 基于 Region 设计(支持动态 Region 大小);
  • 采用 “颜色指针” 技术(通过指针标记对象状态,避免停顿);
  • 并发执行所有阶段(初始标记、并发标记、重新映射等),几乎无停顿。

革命性技术

  • 颜色指针:将对象的标记信息存储在指针的高几位(如 64 位系统中,用 4 位存储状态),无需遍历对象修改标记位;
  • 读屏障:当用户线程访问对象时,通过读屏障自动处理指针的状态转换(如将旧地址映射到新地址)。

适用场景

  • 超大堆内存(>100GB)的应用(如大数据平台、内存数据库);
  • 对延迟要求极高的场景(如高频交易系统);
  • JVM 参数:-XX:+UseZGC(JDK 15 + 默认启用)。

3.6 Shenandoah 收集器:RedHat 的并发收集方案

Shenandoah 是 RedHat 开发的开源收集器(JDK 12 引入),与 ZGC 类似,主打低延迟大堆支持,但实现细节存在差异:

  • 不依赖颜色指针(因专利限制),采用 “连接矩阵” 记录对象引用关系;
  • 重新映射阶段采用并发处理(与 ZGC 的并发重新映射类似);
  • 停顿时间与堆大小无关(主要取决于存活对象数量)。

适用场景

  • 与 ZGC 重叠,适合大堆内存和低延迟场景;
  • JVM 参数:-XX:+UseShenandoahGC(需使用 OpenJDK 的特定版本)。

四、收集器的实战选择:从场景出发的决策指南

不同收集器的特性差异决定了它们的适用场景,以下是基于应用类型的选择建议:

应用类型

核心需求

推荐收集器

典型 JVM 参数

客户端应用

简单、低内存占用

Serial GC

-XX:+UseSerialGC

后台计算

高吞吐量

Parallel GC

-XX:+UseParallelGC -XX:+UseParallelOldGC

Web 应用

低延迟、中等堆内存

CMS + ParNew

-XX:+UseConcMarkSweepGC -XX:+UseParNewGC

大堆服务(10-100GB)

可控延迟、高内存利用率

G1 GC

-XX:+UseG1GC -XX:MaxGCPauseMillis=100

超大堆服务(>100GB)

超低延迟

ZGC/Shenandoah

-XX:+UseZGC或-XX:+UseShenandoahGC

五、GC 日志分析:验证收集器的工作状态

无论选择哪种收集器,都需要通过 GC 日志验证其工作状态。开启 GC 日志的参数:


# 输出详细GC日志,包含时间戳、堆占用变化、停顿时间等

java -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:gc.log -jar app.jar

关键指标解读

  • GC 时间:Minor GC 和 Major GC 的耗时,需与应用的响应时间要求对比;
  • 吞吐量:(总运行时间 - 总 GC 时间)/ 总运行时间,需达到业务预期(如 99%);
  • 内存碎片:老年代的使用率与实际可用内存的差值(碎片率 = 1 - 可用内存 / 总内存),碎片率过高需考虑标记 - 整理算法的收集器。

六、总结与展望

垃圾收集器的技术演进见证了 JVM 对性能的极致追求:从 Serial 的单线程停顿到 ZGC 的微秒级延迟,从 GB 级堆内存到 TB 级支持,每一步突破都为 Java 应用开辟了新的可能性。

选择收集器的核心原则是匹配应用特性:计算密集型应用优先考虑 Parallel GC,延迟敏感型应用选择 G1 或 ZGC,超大堆场景则非

<think>好的,用户之前问了关于Minor GC和Young GC的区别,现在他们又接着问与CMS并用的年轻代垃圾收集器通常是哪一个。我需要先确认用户的知识背景,可能他们已经了解CMS是老年代收集器,但需要明确年轻代的搭配。 首先,回顾CMS(Concurrent Mark Sweep)收集器的特点。CMS主要用于老年代的垃圾回收,以降低停顿时间为目标,采用并发标记清除算法。但CMS自身并不处理年轻代的垃圾回收,所以必须搭配一个年轻代的收集器。 接下来,我需要确定通常与CMS搭配的年轻代收集器。常见的组合是ParNew收集器,因为ParNew是Serial收集器的多线程版本,专门用于年轻代,且能够与CMS配合工作。这一点在Java 8及之前版本中是标准配置。用户可能在使用较旧的系统或特定版本的Java,所以需要指出版本兼容性,比如在JDK 9之后CMS被弃用,所以搭配可能变化。 然后,考虑是否有其他可能性,比如Serial收集器,但Serial是单线程的,通常不会在高并发场景下与CMS搭配使用。Parallel Scavenge收集器虽然也是年轻代的,但由于其设计目标(吞吐量优先)与CMS(低延迟)不兼容,所以一般不会一起使用。需要解释为什么Parallel Scavenge不适合,避免用户混淆。 用户可能的问题还包括为什么选择ParNew而不是其他收集器,这时候需要说明ParNew和CMS的兼容性,以及它们的工作模式如何互补。同时,需要提到启动参数,比如使用-XX:+UseConcMarkSweepGC会自动选择ParNew作为年轻代收集器,这样用户知道如何配置。 还要注意用户可能的误区,比如在较新的JDK版本中CMS已经不再推荐使用,转而使用G1或ZGC等,所以是否需要提及这一点?根据用户的问题,他们可能还在使用旧系统,所以可以简要提到版本变化的影响,但主要回答还是围绕传统的搭配。 最后,总结关键点,明确回答ParNew是通常的选择,并解释原因,同时对比其他可能的收集器为何不适用,确保用户面理解。</think>--- ### **一、核心答案** 与 **CMS(Concurrent Mark-Sweep)收集器** 搭配使用的年轻代垃圾收集器通常是 **ParNew收集器**。这是JVM中经典的“**分代组合**”模式: - **年轻代**:ParNew(多线程复制算法) - **老年代**:CMS(并发标记清除算法) --- ### **二、为什么选择ParNew?** 1. **设计兼容性** - ParNew是 **Serial收集器的多线程版本**,专为年轻代设计,其内存布局和回收策略(复制算法)与CMS对老年代的管理无缝衔接。 - CMS需要与支持 **“同步标记”(同步STW阶段)** 的年轻代收集器配合,而ParNew满足这一要求。 2. **多线程优势** - ParNew通过多线程并行回收年轻代,显著减少GC停顿时间,与CMS的低延迟目标一致。 - 对比单线程的Serial收集器,ParNew更适合多核服务器环境。 3. **参数联动** - 启用CMS时(`-XX:+UseConcMarkSweepGC`),JVM默认自动选择ParNew作为年轻代收集器。 - 示例启动参数: ```bash java -XX:+UseConcMarkSweepGC -XX:+UseParNewGC ... ``` --- ### **三、与其他收集器的兼容性对比** | 年轻代收集器 | 能否与CMS搭配? | 原因 | |--------------------|-----------------|----------------------------------------------------------------------| | **Serial** | ❌ 否 | 单线程回收,与CMS的并发目标冲突 | | **Parallel Scavenge** | ❌ 否 | 设计目标(高吞吐量)与CMS(低延迟)冲突,且内存布局不兼容 | | **G1** | ❌ 否 | G1是整堆回收设计,与CMS分代模型不兼容 | | **ParNew** | ✅ 是 | 多线程、分代复制算法,与CMS协同工作 | --- ### **四、工作流程示例(ParNew + CMS)** 1. **年轻代回收(ParNew)** - **触发条件**:Eden区空间不足。 - **执行过程**: - 暂停用户线程(STW),多线程复制存活对象到Survivor区或晋升至老年代。 - 存活对象年龄超过阈值(`-XX:MaxTenuringThreshold`)直接进入老年代。 2. **老年代回收(CMS)** - **触发条件**:老年代占用达到阈值(`-XX:CMSInitiatingOccupancyFraction`)。 - **执行过程**: - 初始标记(STW) → 并发标记 → 重新标记(STW) → 并发清除。 --- ### **五、注意事项** 1. **JDK版本限制** - **JDK 9+** 中CMS已被标记为废弃(Deprecated),JDK 14+ 中彻底移除。 - 替代方案:使用 **G1**(`-XX:+UseG1GC`)或 **ZGC**(`-XX:+UseZGC`)等现代收集器。 2. **内存碎片问题** - CMS采用标记-清除算法,可能产生内存碎片,需通过参数 `-XX:+UseCMSCompactAtFullCollection` 在Full GC时压缩内存。 3. **晋升失败风险** - 若年轻代晋升速度过快,而CMS无法及时回收老年代空间,可能触发Full GC(Serial Old),导致长暂停。 --- ### **六、总结** - **ParNew是CMS的“黄金搭档”**:两者协同实现年轻代多线程低停顿、老年代并发回收的低延迟目标。 - **适用场景**:对延迟敏感、老年代对象存活周期较长的应用(如Web服务)。 - **演进趋势**:随着CMS的淘汰,建议迁移至G1/ZGC等更先进的收集器。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

练习时长两年半的程序员小胡

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值