Skip to content

JVM GC

GC(Garbage Collection)负责自动识别不再使用的对象,并回收它们占用的内存。学习 GC 不需要先背所有参数,先理解三个问题:对象怎么判断存活、回收会停顿多久、不同回收器在吞吐和延迟之间如何取舍。

GC 选型思路

GC 解决什么问题

问题GC 的作用
对象不用了谁释放自动发现不可达对象并回收
内存碎片怎么办复制、整理或分区回收
应用能不能继续跑在 STW、并行、并发之间取舍
停顿能不能变短使用并发标记、分区回收、读写屏障等技术

GC 不保证没有内存泄漏。只要对象仍然被可达引用链持有,GC 就不能回收。

对象存活判断

HotSpot 主要使用可达性分析。GC 从 GC Roots 出发,沿引用链遍历对象:

text
GC Roots -> 可达对象 -> 继续遍历

不可达对象才可能被回收。

常见 GC Roots:

  • 当前线程栈帧中的局部变量和参数。
  • 静态字段引用的对象。
  • JNI 引用。
  • 活跃线程对象。
  • 被锁持有的对象。
  • JVM 内部结构引用的对象。

排查内存泄漏时,关键不是“对象很大”,而是“谁一直引用它”。heap dump 分析的核心就是找引用链。

STW、并行、并发

概念含义关注点
STW暂停应用线程,GC 独占执行停顿时间
并行多个 GC 线程一起工作,但应用线程通常暂停缩短 GC 工作时间
并发GC 线程和应用线程同时运行降低停顿

并发回收器也不是完全不停顿。初始标记、最终标记、引用处理、类卸载等阶段仍可能 STW。

常见回收器定位

回收器定位常见场景
Serial简单、单线程、低资源占用小堆、工具程序、单核环境
Parallel吞吐量优先批处理、离线计算
ParNewCMS 时代的新生代并行收集器维护老系统
CMS老年代并发标记清除维护低延迟老系统
G1分区、可控停顿、通用服务端大多数服务端应用
ZGC超低延迟、大堆并发回收延迟敏感服务
Shenandoah超低延迟并发回收延迟敏感服务
Epsilon不回收性能测试、短生命周期进程

选型

小应用或命令行工具

优先考虑 Serial。它简单、开销低,复杂回收器的并发能力在小堆上不一定划算。

bash
-XX:+UseSerialGC

批处理或吞吐优先

优先考虑 Parallel。目标是总处理时间更短,而不是每次暂停都很短。

bash
-XX:+UseParallelGC

通用服务端

优先考虑 G1。它在吞吐和停顿之间比较均衡,也是现代服务端常见选择。

bash
-XX:+UseG1GC
-XX:MaxGCPauseMillis=200

MaxGCPauseMillis 是目标,不是硬保证。设置过低可能导致 GC 更频繁、CPU 更高。

极低延迟

考虑 ZGC 或 Shenandoah。它们把大部分工作并发化,适合对长暂停敏感的服务。

bash
-XX:+UseZGC

低延迟回收器可能用更多 CPU 或牺牲部分吞吐,迁移前要压测。

GC 日志

JDK 9+ 推荐统一日志:

bash
-Xlog:gc*:file=gc.log:time,uptime,level,tags

关注字段:

指标含义
Pause 时间应用线程暂停多久
Young / Mixed / Full回收类型
before/after回收前后内存变化
allocation failure是否分配失败触发
humongousG1 大对象相关问题
concurrent cycle并发周期是否来得及完成

排查路径

Full GC 频繁

  1. 看 GC 日志确认 Full GC 触发原因。
  2. 抓 heap dump,分析老年代大对象和引用链。
  3. 判断是内存泄漏、对象生命周期过长,还是堆太小。
  4. 再调整堆大小、晋升策略或回收器。

停顿过长

  1. 先确认停顿发生在 Young、Mixed 还是 Full GC。
  2. 看对象存活率和回收后释放空间。
  3. 看是否有大对象、类卸载、引用处理或系统压力。
  4. 调整目标停顿、堆大小或换低延迟回收器。

GC 后内存不下降

可能原因:

  • 对象仍被静态集合、缓存、线程本地变量持有。
  • 类加载器泄漏。
  • 直接内存或 native 内存增长,不体现在堆里。
  • heap dump 抓取时机不对。

常用命令

bash
jstat -gcutil <pid> 1000
jcmd <pid> GC.heap_info
jcmd <pid> GC.class_histogram
jcmd <pid> GC.heap_dump /tmp/app.hprof

生产环境抓 dump 前确认磁盘空间和停顿风险。

别急,先让缓存热一下。