
这篇用 5 步拆开 JVM OOM 的错误信息、GC 后 live set、heap 与 native memory 证据,并给出可直接复述的 jcmd 取证和止损边界。
-Xmx 调大,再观察能不能撑住。OutOfMemoryError 的含义是 JVM 无法分配对象,而且垃圾回收也没有办法再提供可用内存。它可能来自 Java heap,也可能来自 Metaspace、Compressed Class Space、本地内存分配失败,甚至是一个大数组请求超过了 VM 限制。1我不会先假定这是内存泄漏。先读异常的 detail message,判断是 heap、类元数据还是 native memory;再看 Full GC 后的 live set 有没有持续上涨,最后用 GC 日志、heap histogram、heap dump 或 JFR 把猜测变成证据。扩容只能算止损动作,不能替代定位。
| detail message | 先判断什么 | 面试时要补的边界 |
|---|---|---|
Java heap space | Java heap 无法满足对象分配 | 可能是 -Xmx 太小,也可能是长时间持有对象引用;不能只凭这一行认定泄漏 |
GC Overhead limit exceeded | GC 长时间运行,应用几乎没有进展 | Oracle 文档给出的触发条件是,最近连续 5 次 GC 中,GC 约占 98% 以上时间且回收不到约 2% 的 heap |
Metaspace | 类元数据使用的本地内存达到上限 | 检查是否显式设置了 MaxMetaspaceSize,也要看类加载器和已加载类数量的趋势 |
Compressed class space | 压缩类指针对应的空间不足 | 它和 Metaspace 不是同一个区域,不能混成一句「元空间满了」 |
Out of swap space? 或 Native method | native heap 或 JNI / native 方法的分配失败 | 这时要看 fatal error log 和操作系统内存,而不是只盯着 Java heap |
Java heap space 甚至可能只是应用所需内存超过了当前配置;Metaspace 则是类元数据耗尽;native 分配失败还可能由系统交换空间不足引起。2我会先拿到完整异常文本和堆栈,记录 JVM 版本、-Xms、-Xmx、Metaspace 相关参数,以及容器或主机的内存限制。没有这些上下文,直接说「调大堆」是不完整的。
Java heap space 方向先看 -Xms 和 -Xmx,再看 GC 日志中回收前后占用、Old 区趋势和对象分布。Oracle 给出的 GC 日志示例使用了:-Xlog:gc*,gc+phases=debug:gc.log如果 heap 方向,我会先看回收前后的占用和暂停,再用 heap histogram 找出实例数或字节数增长最快的类;需要确认引用关系时,再取 heap dump 分析 GC root。
jcmd VM.classloader_stats 等工具辅助诊断与类加载器相关的 Metaspace、Compressed Class Space 问题。2如果只是上限配置小,扩大上限能缓解分配失败;如果类加载器或类数量持续增长,扩容只会推迟下一次 OOM,还可能挤压进程的其他内存。我要先确认增长趋势和类加载器是否应该释放。
-Xmx 带偏Out of swap space? 或 native method 分支说明失败点不一定在 Java heap。此时要结合 fatal error log、进程内存映射和操作系统工具排查。Oracle 文档明确把 native heap exhaustion 与 Java heap 问题分开处理。2-Xmx 调大」在这里甚至可能是反方向:容器或主机总内存不变时,Java heap 占得更多,留给线程栈、类元数据、JIT、JNI 和本地库的空间就更少。这个判断要结合实际进程布局和内存限制,不能拿一个固定比例当通用答案。jcmd <pid> GC.heap_info 看 heap 的概况,用 jcmd <pid> GC.class_histogram 看对象数量和大小分布。jcmd <pid> GC.heap_dump filename=heapdump.hprof 取 heap dump。jcmd <pid> VM.classloader_stats,把类加载器和类数量趋势接起来。jcmd 12345 GC.heap_info
jcmd 12345 GC.class_histogram
jcmd 12345 GC.heap_dump filename=/data/heap/heapdump.hprof
jcmd 12345 VM.classloader_statsGC.heap_dump 不是一个无成本的「看一眼」操作。Java 21 的 jcmd 文档把它标为高影响命令,默认还会请求一次 Full GC;生产环境要先确认磁盘空间、暂停风险、权限和流量窗口。3jstat -gcutil <pid> 1000 10 做短时采样,观察 Eden、Old、Metaspace、YGC、FGC 和 GC 时间。不过 Oracle 仍把 jstat 标为 experimental and unsupported,不能把它当成跨版本稳定的监控接口,也不要写脚本依赖它的输出格式。4-Xmx,同时重新验证 GC 频率、暂停时间和请求延迟。-Xms、-Xmx 的含义和 heap 配置边界要结合部署环境回答,不能脱离内存限制报一个固定数值。2MaxMetaspaceSize、CompressedClassSpaceSize 和类加载器统计,再判断是合理的类规模还是异常增长。增加上限可以临时争取时间,但必须配合类加载趋势验证。-XX:+HeapDumpOnOutOfMemoryError 和 -XX:HeapDumpPath=/data/heap。Oracle 文档说明,前者可在发生 OOM 时生成 heap dump,后者指定 dump 的目录或文件路径;配置前要预留磁盘并评估 dump 生成时的暂停和 IO。5JVM OOM 我会先按 detail message 分流,而不是先假设内存泄漏。Java heap space先看 heap 配置、GC 前后占用和 live set;GC Overhead limit exceeded要看 GC 是否长时间运行但回收很少;Metaspace和Compressed class space要转向类元数据、类加载器和类数量趋势;Out of swap space?或 native method 则要看进程和操作系统内存。证据上,我会先采集 GC 日志,用jcmd看 heap 概况和 histogram,只有需要引用链时才取 heap dump。止损动作要和分支匹配:容量不足可以评估扩容,泄漏要修复保留链,native memory 则不能只调-Xmx。最后用 live set、GC 频率和错误是否复现来验证动作有效。
GC.heap_dump 有风险、以及为什么 native memory 不能只靠扩 heap。每个入口都能落到证据和命令,不是把 JVM 参数名背一遍。OutOfMemoryError 不等于 Java heap 泄漏?Java heap space 和 GC Overhead limit exceeded 的差别?jcmd <pid> GC.heap_info、GC.class_histogram 和 GC.heap_dump 的用途与风险?GC.heap_dump 不适合无窗口地在线上执行?-XX:+HeapDumpOnOutOfMemoryError 与 -XX:HeapDumpPath 的配合关系?docs.oracle.com
docs.oracle.com
docs.oracle.com
docs.oracle.com
docs.oracle.com
This story was produced automatically by a channel. One sentence is all it takes for Neodrop to keep producing for you.