JVM GC 调优:内存指标、泄漏排查与线上自救

目标:你能把 GC 调优说清楚为"以指标为导向的系统工程",并掌握线上常见故障(频繁 Full GC、OOM、吞吐下降)的排查套路。

1. GC 调优的第一原则:先确认目标

常见目标:

  • 低延迟:P99/尾延迟更重要(典型:网关、交易)
  • 高吞吐:单位时间处理更多任务(典型:离线、批处理)
  • 稳定性:避免 OOM、避免长时间停顿

不同目标会影响你选择 GC(G1/ZGC)和参数策略。

2. 你必须会的内存结构(够用版)

  • :新生代 + 老年代(以 G1 视角则是 Region)
  • 元空间:类元数据(动态生成类会涨)
  • :线程栈(线程太多会撑爆内存)

面试高频:

  • 为什么线程太多会 OOM:因为每个线程都有栈内存(-Xss)。

3. 看懂 GC 日志:先抓"现象",再猜"根因"

你至少要能回答:

  • GC 是否频繁?
  • Full GC 是否频繁?
  • 单次停顿多长?
  • 回收后内存是否能降下去?

3.1 三个高价值指标

  • 分配速率:新对象产生有多快
  • 晋升速率:对象从新生代进入老年代有多快
  • 回收效率:一次 GC 回收多少、停顿多久

直觉:

  • 分配速率高 -> Minor GC 频繁
  • 晋升速率高 -> 老年代涨得快 -> Full GC/混合回收频繁
  • 回收后降不下来 -> 可能泄漏或缓存无界

4. 频繁 Full GC:最常见的线上事故

4.1 常见根因

  • 老年代增长过快:
    • 大对象直接进入老年代(大数组、批量组装)
    • 短命对象存活过久(大事务、超长链路持有引用)
    • 缓存/集合无界增长
  • GC 无法回收:
    • 内存泄漏

4.2 处理思路(先止血,再根治)

  • 止血
    • 临时扩容(堆/实例数)
    • 降低流量(限流/熔断)
    • 关闭/缩小某些大缓存
  • 根治
    • 找到"谁在持有对象",解决无界引用

5. OOM 分类:不同 OOM 不同打法

5.1 java.lang.OutOfMemoryError: Java heap space

  • 堆爆了:
    • 泄漏或无界缓存
    • 单次请求构造超大对象

5.2 OutOfMemoryError: Metaspace

  • 动态类太多:
    • CGLIB/JDK Proxy 动态生成过多(比如不停创建新的类加载器)
    • 热部署/脚本引擎不释放

5.3 OutOfMemoryError: unable to create new native thread

  • 线程太多:
    • 线程池失控、阻塞堆积
    • -Xss 太大

面试加分点:这类 OOM 不是堆不够,是系统线程资源耗尽。

6. 内存泄漏排查:从"怀疑"到"证据"

6.1 你要找的不是"谁创建了对象",而是"谁持有它不放"

泄漏本质是:对象已经"业务上无用",但仍被引用链持有。

常见泄漏源:

  • 静态集合(static map)
  • ThreadLocal 未清理
  • 缓存无 TTL/无上限
  • 监听器/回调注册未取消

6.2 证据链(通用方法)

  • Dump 堆(heap dump)
  • 找到占用最大的对象类型(Top consumers)
  • 查看 GC root 引用链(是谁把它挂住了)

如果你能说出"从 dump 到引用链"的路径,面试官会认为你做过真实排障。

7. 参数调优:不追求"神配置",追求可解释

7.1 堆大小

  • 堆太小:频繁 GC
  • 堆太大:单次停顿可能更长,且容器场景可能触发 OOMKill

建议:

  • 结合容器内存限制与应用峰值对象量
  • 留出非堆空间(元空间、线程栈、直接内存)

7.2 选择 GC

  • G1:通用默认、较平衡,适合大多数服务端应用
  • ZGC:更偏低延迟(新版本 JVM 支持较好),但要考虑 CPU 开销与版本

面试表达建议:说"我会根据延迟目标和 JVM 版本选择",而不是一句"就用 ZGC"。

8. 线上自救清单(按优先级)

  • 先确认是 GC 导致的 RT:看 STW 时间、Full GC 次数
  • 确认内存回收后是否下降
    • 能下降:可能是流量/分配速率问题
    • 降不下:高度怀疑泄漏/无界缓存
  • 确认线程数:线程爆炸会带来 native thread OOM
  • 短期措施:限流、扩容、降级、重启(谨慎,重启只能缓解不能根治)

9. 面试背诵稿(45 秒)

GC 调优我会先明确目标是低延迟还是高吞吐,然后用 GC 日志和监控指标来驱动。

重点看分配速率、晋升速率、Full GC 频率以及回收后内存是否能降下来。

频繁 Full GC 通常是老年代增长过快或存在无界缓存/泄漏;如果回收后降不下来就要做 heap dump,通过对象占用排行和 GC root 引用链定位是谁持有对象。

OOM 也要分类看:heap space、metaspace 和 native thread 的处理思路完全不同,线上先止血限流扩容,再做根因修复。

相关推荐
AI自动化工坊3 小时前
OpenFang实战指南:用Rust构建高并发AI Agent操作系统
开发语言·人工智能·ai·rust·agent·ai agent
一只叫煤球的猫3 小时前
芋道源码,拉黑我,改变不了你还在搬运别人文章的事实
java·后端·面试
承渊政道3 小时前
【优选算法】(实战剖析链表核心操作技巧)
开发语言·数据结构·c++·vscode·学习·算法·链表
Boop_wu3 小时前
[Java算法] 递归(1)
java·算法·深度优先
wjs20243 小时前
Shell 变量
开发语言
代码改善世界3 小时前
【C++初阶】string类(二):常用接口全解析
开发语言·c++
前端郭德纲3 小时前
JavaScript原生开发与鸿蒙原生开发对比
开发语言·javascript·harmonyos
csbysj20203 小时前
JSP 指令
开发语言