跳转到内容

典型故障定位

用途:遇到 OOM、频繁 GC、Metaspace 溢出、线程创建失败时按图索骥

一、堆内存 OOM(Java heap space)

「一、堆内存 OOM(Java heap space)」小节链接
  1. 确认是泄漏还是容量不足:GC 后老年代基线是否持续抬升(是→泄漏)。
  2. 拿 dump(OOM 自动 dump 或低峰 jmap),用 MAT 看支配树与 Leak Suspects。
  3. 高频原因:无界集合(本地缓存不设上限)、ThreadLocal 未 remove、监听器/回调注册未注销、大结果集一次性加载、字符串拼接累积、缓存 key 使用可变对象。
  4. 修复后加容量测试:给定流量与数据规模验证内存曲线平稳。
触发原因(看 GC 日志) 处理
Allocation Failure + 晋升快 增大 Young 区/降低分配速率(对象复用、批量改流式)
Metadata GC Threshold 提高 MaxMetaspaceSize 或查类加载泄漏(动态代理、Groovy/脚本、反射生成类、频繁热部署)
Humongous Allocation(G1) 调整 Region 大小,避免大数组/大字符串常量
System.gc() 显式触发 -XX:+DisableExplicitGC,并排查调用来源(RMI、部分 NIO 实现)
堆碎片导致提升失败 换收集器或降低堆碎片(G1 整理、必要时重启)
真实容量不足 扩容或拆实例

特征:java.lang.OutOfMemoryError: Metaspace,堆使用正常。定位:jstat -class 看类数量是否单调增长;Arthas classloader -l 找异常 ClassLoader;常见于动态代理/字节码生成库滥用、脚本引擎重复编译、热部署未清理旧加载器。

四、StackOverflowError 与线程问题

「四、StackOverflowError 与线程问题」小节链接
  • 深递归(含隐式递归:JSON 循环引用序列化、代理自调用)→ 改迭代或限制深度。
  • unable to create new native thread → 线程数失控(每功能新建线程、线程池未复用)或系统 ulimit/nproc 限制;先做线程数指标与命名规范。

五、Direct buffer memory / 容器 OOMKilled

「五、Direct buffer memory / 容器 OOMKilled」小节链接
  • 堆外:检查 Netty/NIO 池、MaxDirectMemorySize、Cleaner 是否被阻塞;用 NMT 拆解。
  • 容器:RSS 与 -Xmx 差距过大时,核对线程栈总量、元空间、堆外与 glibc arena;容器内存要留 25-40% 余量。
  • 先分层:是应用计算、GC、还是自旋锁。
  • async-profiler CPU/lock 图 + GC 日志对照;常见根因:锁竞争导致自旋、正则回溯、序列化热点、日志同步写、频繁反射。
  1. 生产常开 GC 日志与 JFR(低开销档)。
  2. 关键指标告警:GC 时间占比、老年代基线、线程数、堆外内存、Metaspace 使用。
  3. 上线前做容量压测并留性能基线,故障时与基线对比。
  4. 建立“dump 与 jfr 归档”流程,避免现场丢失。
误区 修正
OOM 就加堆 先看是泄漏还是容量,加堆只是延后
频繁 Full GC 就换收集器 先定位触发原因(晋升、元空间、大对象、显式 GC)
生产随手 System.gc() 触发全堆停顿,应禁用并查调用方
只看堆 dump 判断内存问题 堆外与线程栈不在 dump 里,需 NMT
  • 《Java 性能权威指南》故障案例章节
  • Arthas / MAT / JDK Mission Control 官方文档
  • 各云厂商关于容器内存与 OOMKilled 的运维文档