跳到正文
Elaine Blog
返回

内存泄漏、OOM 与 Heap Dump 实战

JVM 与性能

Java 内存泄漏不是“内存没有手动释放”,而是无用对象仍被 GC Roots 引用。本文在独立小进程中制造一个有界实验,生成 Heap Dump,并给出从现象到引用链的分析顺序。

Table of contents

Open Table of contents

在隔离目录运行故障样例

下载堆泄漏样例下载, 先编译,再创建只用于实验的输出目录:

mvn package
mkdir -p diagnostics
java -Xms64m -Xmx64m \
  -XX:+HeapDumpOnOutOfMemoryError \
  -XX:HeapDumpPath=diagnostics/heap-leak.hprof \
  -Xlog:gc*:file=diagnostics/gc.log:time,uptime,level,tags \
  -cp target/classes dev.elaine.jvmlab.HeapLeakSample

进程会保留 1 MiB 字节数组,最终抛出 OutOfMemoryError: Java heap space。它只使用 64 MiB 堆,但 Heap Dump 仍可能 包含敏感对象;不要在共享生产目录运行。

如果输出 Could not create the Java Virtual Machine,先检查参数拼写与 JDK 版本。如果 OOM 后没有文件,检查目录存在、 写权限和可用磁盘空间。

先区分 OOM 类型

OutOfMemoryError 是一组资源耗尽结果,不都由 Java 堆泄漏造成:

错误片段可能区域优先证据
Java heap spaceJava 堆Heap Dump、GC 日志
Metaspace类元数据类加载日志、NMT
Direct buffer memory直接缓冲区NMT、缓冲池指标
unable to create native thread线程/系统资源线程数、进程限制、容器内存
进程被 OOMKilled容器总内存cgroup/平台事件、RSS

只有 Java 堆 OOM 能直接用 Heap Dump 解释。进程被系统杀死时,JVM 可能没有机会写任何 Dump。

分析 Heap Dump

使用 Eclipse MAT、VisualVM 或团队批准的分析工具打开 heap-leak.hprof,按以下顺序检查:

  1. 查看 Histogram,确认数量或体积异常的类。
  2. 查看 Dominator Tree,找到**保留大小(Retained Size)**最大的对象。
  3. 查看到 GC Roots 的引用路径,确认是谁让对象继续可达。
  4. 回到源码判断该引用是缓存、监听器、线程本地变量还是生命周期错误。
  5. 修复后使用相同负载比较增长曲线,而不是只确认“不再立刻 OOM”。

样例中 ArrayList 通过内部数组保留所有 byte[],引用链比“字节数组很多”更接近根因。真实系统还应比较两个时间点的 Heap Dump 或 JFR Object Count 增长,区分稳定缓存和持续泄漏。

Oracle 的内存泄漏排查指南推荐使用 Heap Dump 与 JFR 观察增长对象。

修复后增加回归门槛

为泄漏路径建立长时间或重复调用测试,记录稳定后堆占用、完整 GC 后存活集和对象数量。不要断言一次 GC 后内存必须回到 固定字节数;JVM 会保留已提交堆,测试应观察长期存活趋势。

下一步

阅读CPU 飙高、死锁和长暂停排查,把线程、CPU 与 safepoint 证据串成时间线。


分享这篇文章:

上一篇
jcmd、JFR、VisualVM 与 async-profiler
下一篇
CPU 飙高、死锁和长暂停排查