第4章

🗑️ 垃圾收集器详解

深入学习JVM垃圾收集器的工作原理、性能特点和调优策略,掌握不同场景下的最佳选择

学习目标

垃圾收集器概述

垃圾收集器(Garbage Collector,GC)是JVM内存管理的核心组件,负责自动回收不再使用的对象所占用的内存空间。不同的垃圾收集器有着不同的工作原理、性能特点和适用场景。

核心理解

选择合适的垃圾收集器是JVM调优的关键决策之一,需要根据应用的特点、性能要求和硬件环境来综合考虑。

垃圾收集器的分类

按代际分类
  • 新生代收集器:Serial、ParNew、Parallel Scavenge
  • 老年代收集器:Serial Old、Parallel Old、CMS
  • 整堆收集器:G1、ZGC、Shenandoah
按线程数分类
  • 串行收集器:单线程执行,适合小型应用
  • 并行收集器:多线程执行,适合吞吐量优先场景
  • 并发收集器:与应用线程并发执行,适合低延迟场景
按停顿时间分类
  • Stop-The-World:需要暂停所有应用线程
  • 并发收集:大部分时间与应用线程并发执行
  • 增量收集:将收集工作分解为多个小步骤

Serial收集器

Serial收集器是最基本、历史最悠久的垃圾收集器。它是一个单线程收集器,在进行垃圾收集时必须暂停所有工作线程,直到收集结束。

工作原理

适用场景

客户端应用
桌面应用程序、小型工具等对停顿时间不敏感的应用。
小内存环境
堆内存较小(几十MB到一两百MB)的应用环境。
单核处理器
在单核处理器环境下,Serial收集器没有线程交互开销。

配置参数

Serial收集器配置
# 使用Serial收集器(新生代)
-XX:+UseSerialGC

# 设置新生代大小
-Xmn128m

# 设置Eden与Survivor比例
-XX:SurvivorRatio=8

# 设置对象进入老年代的年龄阈值
-XX:MaxTenuringThreshold=15

Parallel收集器

Parallel收集器是JDK 1.4.2引入的多线程垃圾收集器,也被称为"吞吐量优先"收集器。它是Server模式下的默认收集器。

主要特点

收集器组合

Parallel Scavenge
新生代收集器,使用复制算法,多线程并行收集。
Parallel Old
老年代收集器,使用标记-整理算法,多线程并行收集。

配置参数

Parallel收集器配置
# 使用Parallel收集器
-XX:+UseParallelGC
-XX:+UseParallelOldGC

# 设置并行收集线程数
-XX:ParallelGCThreads=4

# 设置最大停顿时间目标(毫秒)
-XX:MaxGCPauseMillis=200

# 设置吞吐量目标(垃圾收集时间占总时间的比例)
-XX:GCTimeRatio=19

# 启用自适应大小策略
-XX:+UseAdaptiveSizePolicy

CMS收集器

CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。它在垃圾收集时能够与应用线程并发执行,大大减少了停顿时间。

工作流程

初始标记
标记GC Roots能直接关联到的对象,需要Stop-The-World,但时间很短。
并发标记
从GC Roots开始遍历整个对象图,与应用线程并发执行,时间较长。
重新标记
修正并发标记期间因用户程序运行而导致标记变动的对象,需要Stop-The-World。
并发清除
清除标记为垃圾的对象,与应用线程并发执行。

优缺点分析

优点
  • 低延迟:并发收集,停顿时间短
  • 响应性好:适合对响应时间敏感的应用
  • 并发执行:大部分收集工作与应用线程并发
缺点
  • CPU敏感:并发阶段会占用CPU资源
  • 浮动垃圾:并发清除阶段产生的垃圾无法处理
  • 内存碎片:使用标记-清除算法,会产生内存碎片
  • Concurrent Mode Failure:可能导致Full GC

配置参数

CMS收集器配置
# 使用CMS收集器
-XX:+UseConcMarkSweepGC
-XX:+UseParNewGC

# 设置CMS触发阈值
-XX:CMSInitiatingOccupancyFraction=75
-XX:+UseCMSInitiatingOccupancyOnly

# 启用类卸载
-XX:+CMSClassUnloadingEnabled

# 设置CMS并发线程数
-XX:ConcGCThreads=2

# 启用增量模式(已废弃)
# -XX:+CMSIncrementalMode

G1收集器

G1(Garbage First)收集器是JDK 7引入的低延迟垃圾收集器,从JDK 9开始成为默认收集器。它将堆内存划分为多个大小相等的Region,能够实现可预测的停顿时间。

核心特性

Region划分
将堆划分为多个大小相等的Region,每个Region可以是Eden、Survivor或Old区。
可预测停顿
可以设置期望的停顿时间目标,G1会尽力在该时间内完成收集。
优先级收集
优先收集垃圾最多的Region,获得最大的收集效率。

收集模式

配置参数

G1收集器配置
# 使用G1收集器
-XX:+UseG1GC

# 设置期望的停顿时间目标(毫秒)
-XX:MaxGCPauseMillis=200

# 设置Region大小(1MB-32MB,必须是2的幂)
-XX:G1HeapRegionSize=16m

# 设置新生代占堆的最小比例
-XX:G1NewSizePercent=20

# 设置新生代占堆的最大比例
-XX:G1MaxNewSizePercent=40

# 设置触发Mixed GC的老年代占用阈值
-XX:G1MixedGCLiveThresholdPercent=85

新一代收集器

随着硬件技术的发展和应用需求的变化,JVM引入了新一代的垃圾收集器,如ZGC和Shenandoah,它们能够在大堆内存下实现极低的停顿时间。

ZGC收集器

超低延迟
停顿时间不超过10ms,与堆大小无关。
大堆支持
支持8MB到16TB的堆大小。
彩色指针
使用彩色指针技术实现并发收集。

Shenandoah收集器

低延迟
停顿时间与堆大小无关,通常在10ms以下。
并发移动
支持并发的对象移动和压缩。
内存效率
相比ZGC有更好的内存利用率。

配置示例

新一代收集器配置
# ZGC配置
-XX:+UseZGC
-XX:+UnlockExperimentalVMOptions  # JDK 11-14需要

# Shenandoah配置
-XX:+UseShenandoahGC
-XX:+UnlockExperimentalVMOptions  # JDK 12-14需要

# 通用配置
-Xmx8g
-XX:+UseTransparentHugePages

收集器选择指南

选择合适的垃圾收集器需要考虑应用的特点、性能要求、硬件环境等多个因素。以下是一些选择建议:

选择决策树

客户端应用
  • 堆内存 < 100MB:Serial GC
  • 单核CPU:Serial GC
  • 对停顿时间不敏感:Serial GC
服务端应用
  • 吞吐量优先:Parallel GC
  • 延迟敏感:G1 GC
  • 大堆内存(>6GB):G1 GC
  • 极低延迟要求:ZGC/Shenandoah
特殊场景
  • 实时系统:ZGC/Shenandoah
  • 大数据处理:Parallel GC
  • Web应用:G1 GC
  • 微服务:G1 GC

性能对比

关键指标对比
  • 吞吐量:Parallel GC > G1 GC > CMS GC > ZGC/Shenandoah
  • 延迟:ZGC/Shenandoah < G1 GC < CMS GC < Parallel GC
  • 内存占用:Serial GC < Parallel GC < CMS GC < G1 GC < ZGC/Shenandoah
  • CPU占用:Serial GC < Parallel GC < G1 GC < CMS GC < ZGC/Shenandoah

调优最佳实践

垃圾收集器的调优是一个持续的过程,需要根据应用的实际运行情况进行监控和调整。

调优步骤

1. 性能分析
分析应用的内存使用模式、对象生命周期、GC频率等。
2. 参数调整
根据分析结果调整堆大小、收集器参数等。
3. 效果验证
通过压力测试验证调优效果,监控关键指标。
4. 持续优化
根据生产环境反馈持续调整和优化。

监控工具

GC监控和分析工具
# 启用GC日志
-XX:+PrintGC
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-XX:+PrintGCApplicationStoppedTime
-Xloggc:gc.log

# JDK 9+的日志配置
-Xlog:gc*:gc.log:time,tags

# 常用监控工具
# 1. jstat - JVM统计信息
jstat -gc  1s

# 2. GCViewer - GC日志分析
# 3. GCPlot.com - 在线GC日志分析
# 4. VisualVM - 可视化监控工具

实战案例

通过实际的调优案例来理解垃圾收集器的选择和参数调整过程。

💻 查看完整代码 - 在线IDE体验

案例分析

调优要点
  • 基准测试:建立性能基准,记录调优前的关键指标
  • 逐步调整:一次只调整一个参数,观察效果
  • 压力测试:在接近生产环境的负载下进行测试
  • 长期监控:关注长期运行的稳定性和性能表现
上一章:内存模型与分配 返回目录 下一章:JIT编译优化