1. 概述
本文我们将介绍如何在Java 中生成 Heap Dump 堆转储的几种方法。
Heap Dump 中文翻译为堆转储,是某个时刻 JVM 内存中所有对象的快照。它们对于解决Java程序中的内存泄漏问题和优化内存使用非常有用。
Heap Dump 通常储存在二进制格式的hprof文件中。 我们可以使用 jhat 或 JVisualVM 之类的工具打开和分析这些文件。或使用Eclipse开源的MAT 的也比较多。
下面,我们将介绍几种工具和方法来生成Heap Dump,并说明它们之间的主要区别。
2. JDK 工具
JDK自带了几种工具可以用来捕获heap dump,这些工具存放在JDK目录下的bin文件夹中。只要此路径在系统环境变量中,我们就可以直接在命令行中运行它们。
2.1. jmap
jmap 是一个用于打印运行中的 JVM 内存统计信息的工具。目标进程可以是本地或远程进程。
要使用 jmap 捕获 heap dump,我们需要添加 dump 选项:
jmap -dump:[live],format=b,file=<file-path> <pid>
dump选项有几个参数:
- live : 如果设置了此参数,则只打印具有活动引用的对象,并丢弃那些准备进行垃圾回收的对象。此参数是可选的
- format=b : 指定dump文件将以二进制格式保存。如果不设置此参数,结果是一样的
- file : dump文件保存的路径
- pid : Java进程ID
示例:
jmap -dump:live,format=b,file=/tmp/dump.hprof 12587
温馨提示,我们可以使用 jps
命令很方便的获取 Java 进程的pid。
注意,jmap 是 JDK 中的一个实验性工具,已经不再受支持。 所以,最好选择使用其他工具代替。
2.2. jcmd
jcmd 是一个非常完整的工具,用于向JVM发送命令。使用此工具需要与运行的Java程序在同一台机器上。
我们可以使用 _GC.heap_dump_命令 来获取heap dump,需要指定进程的pid
和 heap dump 文件输出路径。
jcmd <pid> GC.heap_dump <file-path>
用例:
jcmd 12587 GC.heap_dump /tmp/dump.hprof
和 jmap 一样,生成的 dump 文件是二进制格式的。
2.3. JVisualVM
JVisualVM 是一个用于监控、故障排查和分析Java应用程序的图形化界面工具。界面很简洁,但使用起来非常直观和简单。
在左侧 Java进程列表中,我们鼠标右键选择 “堆 Dump(H)” 选项 ,该工具将生成一个heap dump,并在右侧新建的tab窗口中打开。
生成的dump文件路径可以在 “基本信息” 中看到。
从 JDK 9 开始,Visual VM 不再包括在 Oracle JDK 和 Open JDK 发行版中。 因此,如果使用的是Java 9 或更高版本,则需要从 Visual VM 站点下载 JVisualVM。
3. 自动捕捉 Heap Dump
前面学习了如何通过工具手动获取堆转储。除此之外,可以通过 设置 HeapDumpOnOutOfMemoryError 参数, 在发生 OutOfMemoryError 错误时自动生成堆转储,以帮助我们debug。
java -XX:+HeapDumpOnOutOfMemoryError
默认情况下,dump文件保存在运行应用程序的目录中的 java_pid<pid>.hprof 文件中。如果我们想修改路径,可以通过 HeapDumpPath 选项指定:
java -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=<file-or-dir-path>
使用此参数,当我们的应用程序内存耗尽时,我们将能够在日志中看到包含堆转储的创建文件:
java.lang.OutOfMemoryError: Requested array size exceeds VM limit
Dumping heap to java_pid12587.hprof ...
Exception in thread "main" Heap dump file created [4744371 bytes in 0.029 secs]
java.lang.OutOfMemoryError: Requested array size exceeds VM limit
at com.baeldung.heapdump.App.main(App.java:7)
从上面的日志,我们可以看到堆转储文件被写入到了 java_pid12587.hprof
文件中。
该功能非常有用,并且 运行应用程序时没有额外开销。因此,强烈建议始终启用此选项,尤其是在生产环境中。
最后,这个选项也可以在运行时通过使用 HotSpotDiagnostic MBean 来指定。为此,我们可以使用 JConsole 并将 HeapDumpOnOutOfMemoryError VM 选项设置为 _true_:
我们可以在这篇文章中找到更多关于 MBeans 和 JMX 的信息。
4. JMX
本文将介绍的最后一种方法是使用JMX。我们将使用前面简要介绍过的 HotSpotDiagnostic MBean。这个MBean提供了一个 dumpHeap 方法,该方法接受2个参数:
- outputFile: 转储文件的路径。该文件应该具有 hprof 扩展名
- live: 同前面jmap,如果设置为 true,则只转储内存中的活动对象
在接下来的章节中,我们将展示两种不同的方法来调用这个方法以捕获堆转储。
4.1. JConsole
使用 HotSpotDiagnostic MBean 最简单的方法是通过 JMX 客户端,比如 JConsole。
如果我们打开 JConsole 并连接到一个正在运行的 Java 进程,我们可以导航到 MBeans 标签页,在 com.sun.management 下找到 _HotSpotDiagnostic_。 在操作中,我们可以找到之前描述过的 dumpHeap 方法:
如图所示,我们只需要在 p0 和 p1 文本框中输入参数 outputFile 和 _live_,就可以执行 dumpHeap 操作。
4.2. 编程的方式
另一种方法是通过 Java 代码方式调用它。
要做到这一点,我们首先需要获取一个 MBeanServer 实例,以便获取在应用程序中注册的 MBean。之后,我们只需要获取一个 HotSpotDiagnosticMXBean 的实例,并调用它的 dumpHeap 方法。
让我们看看代码实现:
public static void dumpHeap(String filePath, boolean live) throws IOException {
MBeanServer server = ManagementFactory.getPlatformMBeanServer();
HotSpotDiagnosticMXBean mxBean = ManagementFactory.newPlatformMXBeanProxy(
server, "com.sun.management:type=HotSpotDiagnostic", HotSpotDiagnosticMXBean.class);
mxBean.dumpHeap(filePath, live);
}
请注意,hprof 文件不能被覆盖。 否则会抛出文件已存在的异常:
Exception in thread "main" java.io.IOException: File exists
at sun.management.HotSpotDiagnostic.dumpHeap0(Native Method)
at sun.management.HotSpotDiagnostic.dumpHeap(HotSpotDiagnostic.java:60)
5. 总结
在本教程中,我们展示了多种在 Java 中捕获堆转储的方法。
作为经验法则,我们应该记住在运行 Java 应用程序时始终使用 HeapDumpOnOutOfMemoryError 选项。对于其他目的,只要记住 jmap 的不受支持状态,任何其他工具都可以完美使用。
一如既往,示例的完整源代码可在 GitHub 上找到。