获取JVM堆内存转储的常用方法

1. 堆内存转储简介

堆内存转储(Heap Dump),是指JVM堆内存在某一个时刻的快照,一般使用 hprof 格式的二进制文件来保存。 可用于分析内存泄漏问题,以及Java程序的内存使用优化。

常见的内存转储分析工具包括: jhatJVisualVM, 以及基于Eclipse的 MAT工具.

下面介绍获取堆内存转储的常用方法。

2. 使用JDK内置工具

JDK内置了很多诊断工具,位于 JDK_HOME/bin 目录下,一般来说这个目录可以包含到系统PATH路径中,可以直接在命令行中调用。
JDK内置的堆内存转储工具包括:

2.1 jmap 工具

jmap 可用来输出JVM内存的统计信息,支持访问本地JVM,以及远程JVM实例。

使用 -dump 选项来获取堆内存转储,命令为:

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

-dump: 选项后面, 可以指定以下参数:

  • live: 可选参数;表示只输出存活对象,也就是会先执行一次FullGC来清除可以被回收的部分。
  • format=b: 可选参数, 指定 dump 文件为二进制格式(binary format). 在堆内存转储时,默认就是二进制格式。
  • file: 指定转储文件的保存路径。
  • pid: 指定Java进程的pid。

使用示例如下:

jmap -dump:live,format=b,file=/tmp/dump.hprof 12587
# 或者
jmap -dump:file=/tmp/dump.hprof 12587

JVM进程的 pid 一般是通过 jps 命令获取,当然也可以使用通过 jcmd 命令,或者 ps 命令查询。

2.2 jcmd 工具

jcmd 是一个全能的命令行诊断工具,其工作原理是将要执行的命令发送给具体的JVM实例,所以只支持在本地机器上使用。

其中的一个命令是 GC.heap_dump, 可以用来获取堆内存转储,只需要指定 pid 即可,命令格式为:

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

使用示例也类似:

jcmd 12587 GC.heap_dump /tmp/dump.hprof

和 jmap 一样,内存转储文件都是二进制的。
因为是把命令当做参数传给具体的JVM来执行,所以文件路径最好是绝对路径。

2.3 JVisualVM 工具

JVisualVM是一款图形界面工具,可用来监控、分析和诊断Java应用程序。

img

图形界面简单优雅,而且功能强大,支持众多插件。

其中的一个功能是抓取堆内存快照,打开程序,在可见的Java进程上点击鼠标右键,选择“堆内存转储(Heap Dump)”, 即可创建新的内存转储文件,并自动在新标签页中打开。

在这里插入图片描述

完成之后,我们可以在基本信息(Basic Info)中看到转储文件的目录信息。

3. 自动执行堆内存转储

前面介绍的工具都是手工执行的,有时候,我们希望在发生内存溢出错误 java.lang.OutOfMemoryError 时, JVM自动执行堆内存转储,以方便事后进行排查和分析。 JVM提供了一个命令行启动参数 HeapDumpOnOutOfMemoryError, 使用的格式为:

java -XX:+HeapDumpOnOutOfMemoryError

如果不用 HeapDumpPath 选项指定转储路径,则会自动保存到启动目录下,文件名的格式为: java_pid<pid>.hprof

指定 HeapDumpPath 参数的使用示例如下:

java -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=<file-or-dir-path>

Java程序在运行过程中如果发生内存溢出,则会在日志中看到类似这样的内容:

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 来动态设置,比如在JMX客户端之中设置 HeapDumpOnOutOfMemoryError 选项的值为 true:

在这里插入图片描述

MBeans 和 JMX 的更多信息请参考: JMX 与相关工具:山高月小,水落石出

4. JMX方式

最后,我们来看看怎么通过JMX方式获取堆内存转储。 本质上是调用 HotSpotDiagnostic 这个MBean,其提供了一个 dumpHeap 方法, 参数为:

  • outputFile: 转储文件的路径, 一般以 .hprof 后缀结尾。
  • live: 如果设置为 true, 则只转储存活对象, 和 jmap 的使用类似。

下面是使用示例:

4.1. JMX客户端工具

HotSpotDiagnostic MBean 最容易操作的方式是图形界面客户端, 例如 JConsoleJVisualVM 等。

打开 JConsole, 连接到指定的Java进程, 切换到 MBeans 页签, 定位到 com.sun.management. 包下面的 HotSpotDiagnostic, 执行对应的 dumpHeap 方法即可。

在这里插入图片描述

然后在 p0p1 槽位填写对应的参数 outputFile, live, 执行 dumpHeap 即可。

4.2. 编程方式调用

首先,需要获取 MBeanServer 实例,然后再获取系统注册的 HotSpotDiagnosticMXBean MBean, 接着调用 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. 小结

本文介绍了几种获取堆内存转储的方法。 简单总结一下:

  1. 强烈建议指定JVM启动参数 HeapDumpOnOutOfMemoryError.
  2. 如果 jmap 不能使用,可以使用其他的替代方式,例如 jcmd、JVisualVM、JMX等等。
  3. 本文对应的代码请参考: GitHub仓库.
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页