源本科技 | 码上会

JVM 垃圾回收器

2026/01/23
40
0

学习目标

  • 了解 JVM 垃圾回收的基本原理与作用

  • 掌握主流垃圾回收器的类型、特点及适用场景

  • 学会通过 JVM 参数配置和优化垃圾回收行为

  • 理解不同垃圾回收器在线程使用和暂停时间上的差异


什么是垃圾回收

JVM 垃圾回收(Garbage Collection, GC)是 Java 内存管理的核心机制。它自动识别并释放堆中不再被引用的对象所占用的内存,从而防止内存泄漏、提升程序性能。JVM 使用 标记 - 清除(Mark and Sweep) 算法作为基础,通过追踪活跃对象并回收不可达对象的内存空间。


垃圾回收中的线程控制

控制垃圾回收线程数量

多线程垃圾回收器(如 Parallel GC)允许我们指定用于 GC 的线程数:

java -XX:+UseParallelGC -XX:ParallelGCThreads=4 -jar MyApp.jar

ParallelGCThreads 的值通常设置为 CPU 核心数,但可根据应用负载调整。

限制 GC 暂停时间

对于对延迟敏感的应用,可通过以下参数限制最大 GC 暂停时间(单位:毫秒):

java -XX:+UseParallelGC -XX:MaxGCPauseMillis=200 -jar MyApp.jar

注意:该参数是“目标”而非硬性保证,JVM 会尽力满足,但不保证绝对达成。


主流垃圾回收器

Serial

  • 最简单的 GC 实现

  • 使用 单线程 执行所有 GC 工作

  • GC 期间 完全暂停应用线程(Stop-The-World)

  • 适用于 小型应用嵌入式系统(内存资源有限)

启用方式:

java -XX:+UseSerialGC -jar MyApp.jar

Parallel

吞吐量优先

  • Java 8 的默认 GC

  • 又称 吞吐量收集器

  • 使用 多个线程 并行执行 GC,充分利用 CPU 资源

  • 适合 批处理、后台任务 等可容忍暂停的高性能场景

与 Serial GC 的核心区别:

  • Serial:单线程,简单但慢

  • Parallel:多线程,更快,但仍有 STW 暂停(包括 Minor GC)

启用方式(Java 9+ 需显式指定):

java -XX:+UseParallelGC -jar MyApp.jar

CMS

  • 目标:最小化暂停时间

  • 大部分工作 与应用线程并发执行

  • 仅在以下两个阶段暂停应用:

    1. 初始标记(Initial Mark)

    2. 重新标记(Remark)

  • 适用于 低延迟要求 的场景,如 Web 服务器、实时交互系统

与 Parallel GC 的关键区别:

  • Parallel:追求吞吐量,暂停时间较长

  • CMS:追求低延迟,允许应用在 GC 期间继续运行

注意:CMS 自 Java 14 起已被 弃用,Java 17 中 彻底移除

启用方式(旧版本 JDK):

java -XX:+UseConcMarkSweepGC -jar MyApp.jar

G1

  • JDK 7 引入,Java 9 起成为默认 GC

  • 专为 大堆内存(> 4 GB) 设计

  • 将堆划分为 多个等大区域(Region,1 MB ~ 32 MB)

  • 采用 “垃圾优先”策略:优先回收垃圾最多的 Region

  • 支持 堆内存压缩,避免碎片化

优势:

  • 可预测的暂停时间

  • 高吞吐与低延迟兼顾

  • 替代了 CMS 成为主流选择

启用方式(Java 8 及更早版本):

java -XX:+UseG1GC -jar MyApp.jar

Java 9+ 默认即为 G1,无需显式开启。


ZGC

  • Java 11 引入(实验性),Java 15 转正

  • 超低延迟 GC,暂停时间通常 < 10 毫秒

  • 支持 TB 级堆内存

  • 几乎全程并发执行,对应用影响极小

适用场景:对延迟极度敏感的大型应用(如金融交易、实时分析)

启用方式:

java -XX:+UseZGC -jar MyApp.jar

Shenandoah

  • Java 12 引入(OpenJDK 特有)

  • 与 ZGC 类似,目标是 极低暂停时间

  • 通过 并发压缩 实现高效内存回收

  • 暂停时间与堆大小 无关

启用方式:

java -XX:+UseShenandoahGC -jar MyApp.jar

注意:Shenandoah 在 Oracle JDK 中默认不可用,需使用 OpenJDK 构建版本。


常用垃圾回收参数

通用内存与调试参数

参数

说明

-Xms<size>

设置初始堆大小(如 -Xms2g

-Xmx<size>

设置最大堆大小(如 -Xmx8g

-XX:MaxMetaspaceSize=<size>

限制元空间最大内存

-XX:+HeapDumpOnOutOfMemoryError

发生 OOM 时生成堆转储

-XX:HeapDumpPath=<path>

指定堆转储文件保存路径

-XX:+PrintGCDetails

打印详细 GC 日志

-XX:+UseContainerSupport

优化容器环境下的内存感知

垃圾回收器选择与调优

参数

说明

-XX:+UseSerialGC

启用 Serial GC

-XX:+UseParallelGC

启用 Parallel GC

-XX:+UseConcMarkSweepGC

启用 CMS GC(已废弃)

-XX:+UseG1GC

启用 G1 GC

-XX:+UseZGC

启用 ZGC

-XX:+UseShenandoahGC

启用 Shenandoah GC

-XX:ParallelGCThreads=<n>

设置 Parallel / G1 的 GC 线程数

-XX:G1HeapRegionSize=<size>

设置 G1 区域大小(如 16m


垃圾回收器对比


重点总结

  • Serial GC:适合小型应用,资源消耗最低。

  • Parallel GC:默认于 Java 8,强调吞吐量,适合后台任务。

  • CMS:已淘汰,曾用于低延迟场景。

  • G1 GC:现代应用首选,兼顾吞吐与延迟,Java 9+ 默认。

  • ZGC / Shenandoah:面向未来,支持超大堆与毫秒级暂停,适用于高实时性系统。

  • 通过 JVM 参数可灵活控制 GC 行为,包括线程数、暂停目标、堆大小等。


思考题

  1. 为什么 CMS 垃圾回收器最终被 G1 取代?从内存碎片和维护复杂度角度分析。

  2. 在一个需要处理 64 GB 堆内存且要求 99% 的 GC 暂停时间低于 10 毫秒的系统中,应选择哪种垃圾回收器?为什么?

  3. 如果你的应用部署在 Docker 容器中,仅分配了 2 GB 内存,你会优先考虑哪种 GC?如何通过 JVM 参数优化其行为?