前置知识: Java

JVM垃圾回收

3 minAdvanced2026/6/14

GC算法与垃圾回收器

概述

JVM 垃圾回收(GC)是自动内存管理的核心机制,负责回收不再使用的对象占用的内存空间。理解 GC 算法、分代模型和各种回收器的特点,是进行 JVM 调优和排查内存问题的基础。

基础概念

GC 算法

算法原理优点缺点
标记-清除标记存活对象,清除未标记对象实现简单产生内存碎片
标记-整理标记后将存活对象移向一端无碎片移动对象开销大
复制算法将存活对象复制到另一半空间无碎片、效率高可用空间减半
分代收集根据对象存活时间选择不同算法综合效率最高实现复杂

分代模型

+-------------------+-------------------+
|     新生代         |      老年代       |
| Eden | S0 | S1    |                   |
+-------------------+-------------------+
  • 新生代:新对象在 Eden 区分配,Minor GC 频率高但速度快
  • 老年代:长期存活的对象晋升到老年代,Major GC/Full GC 频率低但停顿长
  • 对象晋升:默认经历 15 次 Minor GC 仍存活的对象晋升到老年代

GC

说明
Minor GC回收新生代,频率高,停顿短
Major GC回收老年代(不同回收器定义不同)
Full GC回收整个堆和方法区,停顿最长
Mixed GCG1 回收新生代和部分老年代区域

快速上手

查看 GC 日志

# Java 8 查看 GC 日志
java -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:gc.log -jar app.jar

# Java 9+ 使用统一日志框架
java -Xlog:gc*:file=gc.log:time,uptime,level,tags -jar app.jar

# 查看 GC 摘要信息
jstat -gcutil <pid> 1000  # 每秒打印一次 GC 统计

选择垃圾回收

# Serial 收集器:单线程,适合客户端应用
java -XX:+UseSerialGC -jar app.jar

# Parallel 收集器:多线程,吞吐量优先(Java 8 默认)
java -XX:+UseParallelGC -jar app.jar

# G1 收集器:分区收集,停顿可控(Java 9+ 默认)
java -XX:+UseG1GC -jar app.jar

# ZGC 收集器:超低延迟(Java 15+ 生产可用)
java -XX:+UseZGC -jar app.jar

详细用法

G1 收集器配置

# G1 收集器核心参数
java -XX:+UseG1GC \
  -XX:MaxGCPauseMillis=200 \       # 目标最大停顿时间(毫秒)
  -XX:G1HeapRegionSize=8m \        # Region 大小(1/2/4/8/16/32MB)
  -XX:InitiatingHeapOccupancyPercent=45 \ # 触发并发标记的堆占用比例
  -XX:G1MixedGCCountTarget=8 \     # Mixed GC 最大次数
  -XX:G1ReservePercent=10 \        # 保留空间比例,防止晋升失败
  -Xms2g -Xmx2g \                  # 堆大小(建议 Xms=Xmx)
  -jar app.jar

ZGC 收集器配置

# ZGC 收集器核心参数
java -XX:+UseZGC \
  -XX:ZAllocationSpikeTolerance=2 \  # 分配峰值容忍度
  -XX:SoftMaxHeapSize=1g \           # 软最大堆大小
  -XX:ConcGCThreads=4 \              # 并发 GC 线程数
  -Xms2g -Xmx4g \                    # 堆大小范围
  -jar app.jar

# JDK 21 分代 ZGC
java -XX:+UseZGC -XX:+ZGenerational -jar app.jar

常见 JVM 调优参数

# 堆内存设置
-Xms4g                    # 初始堆大小
-Xmx4g                    # 最大堆大小
-Xmn2g                    # 新生代大小
-XX:MetaspaceSize=256m    # 元空间初始大小
-XX:MaxMetaspaceSize=512m # 元空间最大大小

# GC 通用参数
-XX:+UseG1GC              # 使用 G1 收集器
-XX:MaxGCPauseMillis=200  # 目标最大停顿时间
-XX:+HeapDumpOnOutOfMemoryError  # OOM 时自动生成堆转储
-XX:HeapDumpPath=/logs/   # 堆转储文件路径

# 线程栈设置
-Xss512k                  # 每个线程的栈大小

常见场景

排查内存泄漏

# 第一步:观察 GC 行为
jstat -gcutil <pid> 1000 5  # 每1秒打印5次

# 第二步:查看对象统计
jmap -histo <pid> | head -20  # 查看占用内存最多的对象类型

# 第三步:生成堆转储
jmap -dump:format=b,file=heap.hprof <pid>

# 第四步:使用 MAT 或 VisualVM 分析堆转储
# 重点关注:Dominator Tree、Leak Suspects

Full GC 频繁排查

# 常见原因及排查思路:
# 1. 老年代空间不足 -> 检查大对象分配
# 2. 元空间不足 -> 检查动态类生成(如 CGLIB)
# 3. 显式调用 System.gc() -> 检查代码或禁用
# 4. 内存泄漏 -> 使用堆转储分析

# 禁用显式 GC 调用
java -XX:+DisableExplicitGC -jar app.jar

大堆内存调优

# 16GB 堆内存的 G1 配置建议
java -XX:+UseG1GC \
  -Xms16g -Xmx16g \
  -XX:MaxGCPauseMillis=200 \
  -XX:G1HeapRegionSize=16m \
  -XX:InitiatingHeapOccupancyPercent=40 \
  -XX:ParallelGCThreads=8 \
  -XX:ConcGCThreads=4 \
  -XX:+UseLargePages \
  -jar app.jar

注意事项

  • Xms 和 Xmx 建议设置为相同值,避免运行时堆大小调整的开销
  • G1 的 MaxGCPauseMillis 是目标值而非保证值,实际停顿可能超过
  • 频繁 Full GC 通常意味着内存分配有问题,需要排查而非仅调参
  • ZGC 适合大堆内存(>4GB)和低延迟要求的场景
  • GC 调优应基于实际负载和 GC 日志,不要凭经验猜测
  • 生产环境务必开启 HeapDumpOnOutOfMemoryError

进阶用法

GC 日志分析

# 使用 GCEasy 在线分析 GC 日志
# 访问 https://gceasy.io 上传 gc.log 文件

# 使用 JDK 自带工具分析
# 生成 GC 日志
java -Xlog:gc*:file=gc.log -jar app.jar

# 使用 jstat 实时监控
jstat -gc <pid> 1000  # 每秒打印 GC 统计信息

# 关键指标:
# YGC  - Young GC 次数
# YGCT - Young GC 总耗时
# FGC  - Full GC 次数
# FGCT - Full GC 总耗时
# GCT  - GC 总耗时

内存分配策略

// 大对象直接进入老年代
// -XX:PretenureSizeThreshold=1M  超过1MB的对象直接在老年代分配
// 仅对 Serial 和 ParNew 收集器有效

// 长期存活对象进入老年代
// -XX:MaxTenuringThreshold=15  默认15次GC后晋升

// 动态年龄判断
// 如果 Survivor 中相同年龄对象总大小超过 Survivor 空间一半,
// 大于等于该年龄的对象直接晋升老年代

// 空间分配担保
// -XX:-HandlePromotionFailure  关闭空间分配担保(JDK 6+ 默认开启)