1.简介

在本文中,我们将展示在 Java 中捕获堆转储的不同方法。

堆转储是 JVM 中特定时刻内存中的所有对象的快照。它们对于解决内存泄漏问题并优化 Java 应用程序中的内存使用情况非常有用。

堆转储通常以二进制格式 hprof 文件存储。我们可以使用 jhat 或 JVisualVM 等工具打开和分析这些文件。此外,对于Eclipse用户来说,使用MAT是很常见的。

在接下来的部分中,我们将介绍多个工具和方法来生成堆转储,并展示它们之间的主要区别。

2.JDK Tools

JDK 附带了多种工具,以不同方式捕获堆转储。所有这些工具都位于 JDK 主目录中的 bin 文件夹下。因此,只要此目录包含在系统路径中,我们就可以从命令行启动它们。

因此,我们将演示如何使用这些工具来捕获堆转储。

在接下来的部分中,我们将演示如何使用这些工具来捕获堆转储。

2.1 jmap

jmap 是一种工具,用于打印有关正在运行的 JVM 中内存的统计信息。我们可以将其用于本地或远程进程。参数如下:

jmap -dump:[live],format=b,file=<file-path> <pid>

除了该选项,我们还应该指定几个参数:

  • live:如果设置它,它仅打印具有活动引用的对象,并丢弃那些准备垃圾回收的对象。此参数是可选的
  • format-b:指定转储文件将以二进制格式。如果未设置,则结果相同
  • file:转储将写入的文件
  • 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。我们只需指定进程的 pid 和输出文件路径,就可以使用它来获取堆转储:

jcmd <pid> GC.heap_dump <file-path>

比如:

jcmd 12587 GC.heap_dump /tmp/dump.hprof

与 jmap 一样,生成的转储采用二进制格式。

2.3 JVisualVM

JVisualVM 是一个具有图形用户界面的工具,它使我们能够监视、排除故障和分析 Java 应用程序。GUI 简单但非常直观,易于使用。

它的许多选项之一允许我们捕获堆转储。如果我们右键单击 Java 进程并选择"Heap Dump"选项,该工具将创建一个堆转储并在新选项卡中打开它:

android 捕获堆转储_java

3.自动捕捉Heap Dump

我们在前几节中展示的所有工具都旨在在特定时间手动捕获堆转储。在某些情况下,我们希望在发生 java.lang.OutOfMemoryError 时获取堆转储,以便帮助我们调查错误。

在这些情况下,Java 提供 HeapDumpOnOutOfMemoryError 命令行选项,该选项在引发 java.lang.OutOfMemoryError 时生成堆转储:

java -XX:+HeapDumpOnOutOfMemoryError

默认情况下,它将转储存储在java_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 文件。

正如我们所看到的,此选项非常有用,使用此选项运行应用程序时没有开销。因此,强烈建议始终使用此选项,尤其是在生产中。

最后,也可以在运行时使用热点诊断 MBean 指定此选项。为此,我们可以使用 JConsole 并将 HeapDumpOnOutOfMemoryError VM 设置为 true:

android 捕获堆转储_Java_02

4.JMX

本文中介绍的最后一种方法是使用 JMX。我们将使用上一节中简要介绍的HotSpotDiagnostic MBean。此 MBean 提供接受 2 个参数的转储堆方法:

outputFile:转储的文件的路径。该文件应具有 hprof 扩展名
live:如果设置为 true,则仅转储内存中的活动对象,正如我们之前在 jmap 中看到的那样
在接下来的部分中,我们将展示 2 种不同的方法来调用此方法以捕获堆转储。

4.1 JConsole

使用HotSpotDiagnostic MBean 的最简单方法是使用 JMX 客户端(如 JConsole)。

如果我们打开 JConsole 并连接到正在运行的 Java 进程,我们可以导航到 MBeans 选项卡,并在 com.sun.management下找到"HotSpotDiagnostic"。在操作中,我们可以找到之前介绍的 dumpHeap 方法:

android 捕获堆转储_jvm_03

4.2 Programmatic Way

使用 HotSpotDiagnostic MBean 的另一种方法是从 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应用程序时始终使用HeapDumpOnOutOfOfOfSOnOfOfSOnErrorError选项。出于其他目的,只要我们记住 jmap 不受支持的状态,任何其他工具都可以完美使用。

参考代码