910 字
5 分钟
JVM 基础排障:OOM、频繁 GC 与 CPU 飙高
基础排障的目标是把现象对应到证据:CPU 看热线程与栈,内存看错误类型、GC 和对象引用,不能一遇到问题就加堆或加线程。
OOM 与 StackOverflowError
| 现象 | 常见方向 |
|---|---|
| Java heap space | 堆对象过多、大批量加载、无界缓存或队列 |
| Metaspace | 大量类加载、动态生成类、类加载器无法释放 |
| Direct buffer memory | 堆外直接内存占用与释放问题 |
| unable to create native thread | 线程过多、系统限制或本地内存不足 |
| StackOverflowError | 递归太深或调用链过深,属于栈空间问题 |
OOM 不只来自 Java 堆。容器被内存限制触发 OOM kill 时,也可能没有 Java 层 OOM 异常和堆转储,需要查容器或系统事件。
内存泄漏是“不再需要的对象仍被引用而无法释放”;内存溢出是无法满足分配需求。泄漏可能导致溢出,但一次性加载巨大数据也可能溢出,未必泄漏。
内存与频繁 GC 怎么查?
- 确认错误和时间范围,查看发布、流量、批量任务等变化。
- 结合 GC 日志看回收频率、停顿、回收前后占用,判断分配过快还是存活对象持续累积。
- 查看对象直方图,必要时获取堆转储,分析大对象、集合和到 GC Roots 的引用路径。
- 根据证据限制批次、队列和缓存,修复残留引用,再评估容量配置。
堆转储中对象多只能提示方向;判断泄漏需要结合对象用途、引用路径和占用随时间的变化。Full GC 后仍持续增长的占用值得关注,但不能仅凭一次日志断言泄漏。
CPU 高怎么查?
在 Linux 上,先定位进程,再通过 top -H -p <pid> 等查看热线程;将线程 ID 转为十六进制,与线程转储中的 nid 对照,查看它正在执行什么。多次采样区分长期热点和瞬时波动。
重点查死循环、过度重试、热点计算、序列化、频繁 GC 等。锁等待通常直接体现为等待与延迟,不应看到阻塞就断言它一定消耗大量 CPU。
常用工具与参数
jcmd <pid> Thread.printjcmd <pid> GC.heap_infojcmd <pid> GC.class_histogramjcmd <pid> GC.heap_dump /path/app.hprof命令是否可用取决于 JDK、权限和运行环境。直方图、堆转储等可能带来停顿、额外 GC 和大文件写入,应评估影响后在合适窗口操作。
-Xms:初始堆大小;-Xmx:最大堆大小。-Xss:线程栈大小;线程越多,总栈内存也越大。-XX:MaxMetaspaceSize:元空间上限,不是堆上限。-XX:+HeapDumpOnOutOfMemoryError:在支持的 OOM 场景生成堆转储,配合路径和磁盘空间规划。- JDK 9+ 可以通过
-Xlog:gc*等统一日志选项观察 GC;旧版本参数不同。
面试回答模板
先确认现象和运行环境,OOM 要按错误类型区分堆、元空间、直接内存和线程资源;频繁 GC 要看回收前后占用与分配速度,必要时分析堆和引用路径;CPU 高要定位热线程并对照线程栈,多次采样确认热点。修复应根据证据选择限流、容量约束或代码改动,而不是直接扩大所有资源。
自测
- 堆未满就不会 OOM 吗?不是,可能耗尽其他内存或线程资源。
- 频繁 Full GC 一定是泄漏吗?不一定。
- 堆转储命令是完全无开销的吗?不是,要评估停顿与磁盘成本。
JVM 基础排障:OOM、频繁 GC 与 CPU 飙高
https://blog.xqcherry.top/posts/interview/jvm/jvm-basic-troubleshooting/ 目录