JVM GC 调优:内存指标、泄漏排查与线上自救
目标你能把 GC 调优说清楚为“以指标为导向的系统工程”并掌握线上常见故障频繁 Full GC、OOM、吞吐下降的排查套路。1. GC 调优的第一原则先确认目标常见目标低延迟P99/尾延迟更重要典型网关、交易高吞吐单位时间处理更多任务典型离线、批处理稳定性避免 OOM、避免长时间停顿不同目标会影响你选择 GCG1/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.1java.lang.OutOfMemoryError: Java heap space堆爆了泄漏或无界缓存单次请求构造超大对象5.2OutOfMemoryError: Metaspace动态类太多CGLIB/JDK Proxy 动态生成过多比如不停创建新的类加载器热部署/脚本引擎不释放5.3OutOfMemoryError: unable to create new native thread线程太多线程池失控、阻塞堆积-Xss太大面试加分点这类 OOM 不是堆不够是系统线程资源耗尽。6. 内存泄漏排查从“怀疑”到“证据”6.1 你要找的不是“谁创建了对象”而是“谁持有它不放”泄漏本质是对象已经“业务上无用”但仍被引用链持有。常见泄漏源静态集合static mapThreadLocal 未清理缓存无 TTL/无上限监听器/回调注册未取消6.2 证据链通用方法Dump 堆heap dump找到占用最大的对象类型Top consumers查看 GC root 引用链是谁把它挂住了如果你能说出“从 dump 到引用链”的路径面试官会认为你做过真实排障。7. 参数调优不追求“神配置”追求可解释7.1 堆大小堆太小频繁 GC堆太大单次停顿可能更长且容器场景可能触发 OOMKill建议结合容器内存限制与应用峰值对象量留出非堆空间元空间、线程栈、直接内存7.2 选择 GCG1通用默认、较平衡适合大多数服务端应用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 的处理思路完全不同线上先止血限流扩容再做根因修复。