典型故障定位
用途:遇到 OOM、频繁 GC、Metaspace 溢出、线程创建失败时按图索骥
一、堆内存 OOM(Java heap space)
「一、堆内存 OOM(Java heap space)」小节链接- 确认是泄漏还是容量不足:GC 后老年代基线是否持续抬升(是→泄漏)。
- 拿 dump(OOM 自动 dump 或低峰
jmap),用 MAT 看支配树与 Leak Suspects。 - 高频原因:无界集合(本地缓存不设上限)、
ThreadLocal未 remove、监听器/回调注册未注销、大结果集一次性加载、字符串拼接累积、缓存 key 使用可变对象。 - 修复后加容量测试:给定流量与数据规模验证内存曲线平稳。
二、频繁 Full GC
「二、频繁 Full GC」小节链接| 触发原因(看 GC 日志) | 处理 |
|---|---|
| Allocation Failure + 晋升快 | 增大 Young 区/降低分配速率(对象复用、批量改流式) |
| Metadata GC Threshold | 提高 MaxMetaspaceSize 或查类加载泄漏(动态代理、Groovy/脚本、反射生成类、频繁热部署) |
| Humongous Allocation(G1) | 调整 Region 大小,避免大数组/大字符串常量 |
| System.gc() 显式触发 | -XX:+DisableExplicitGC,并排查调用来源(RMI、部分 NIO 实现) |
| 堆碎片导致提升失败 | 换收集器或降低堆碎片(G1 整理、必要时重启) |
| 真实容量不足 | 扩容或拆实例 |
三、Metaspace 溢出
「三、Metaspace 溢出」小节链接特征: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% 余量。
六、CPU 高但吞吐低
「六、CPU 高但吞吐低」小节链接- 先分层:是应用计算、GC、还是自旋锁。
- async-profiler CPU/lock 图 + GC 日志对照;常见根因:锁竞争导致自旋、正则回溯、序列化热点、日志同步写、频繁反射。
通用防线
「通用防线」小节链接- 生产常开 GC 日志与 JFR(低开销档)。
- 关键指标告警:GC 时间占比、老年代基线、线程数、堆外内存、Metaspace 使用。
- 上线前做容量压测并留性能基线,故障时与基线对比。
- 建立“dump 与 jfr 归档”流程,避免现场丢失。
常见误区
「常见误区」小节链接| 误区 | 修正 |
|---|---|
| OOM 就加堆 | 先看是泄漏还是容量,加堆只是延后 |
| 频繁 Full GC 就换收集器 | 先定位触发原因(晋升、元空间、大对象、显式 GC) |
生产随手 System.gc() |
触发全堆停顿,应禁用并查调用方 |
| 只看堆 dump 判断内存问题 | 堆外与线程栈不在 dump 里,需 NMT |
延伸阅读
「延伸阅读」小节链接- 《Java 性能权威指南》故障案例章节
- Arthas / MAT / JDK Mission Control 官方文档
- 各云厂商关于容器内存与 OOMKilled 的运维文档