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 方法:

如图所示,我们只需要在 p0p1 文本框中输入参数 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 上找到。