◀返回
从原生可执行文件创建堆转储
您可以创建正在运行的可执行文件的堆转储以监控其执行。就像任何其他 Java 堆转储一样,它可以使用 VisualVM 工具打开。
要启用堆转储支持,原生可执行文件必须使用 --enable-monitoring=heapdump
选项构建。然后可以通过以下方式创建堆转储
- 使用 VisualVM 创建堆转储。
- 命令行选项
-XX:+HeapDumpOnOutOfMemoryError
可用于在原生可执行文件耗尽 Java 堆内存时创建堆转储。 - 使用
-XX:+DumpHeapAndExit
命令行选项转储原生可执行文件的初始堆。 - 在运行时向应用程序发送
SIGUSR1
信号来创建堆转储。 - 使用
org.graalvm.nativeimage.VMRuntime#dumpHeap
API 以编程方式创建堆转储。
下面描述了所有方法。
注意:默认情况下,堆转储将创建在当前工作目录中。
-XX:HeapDumpPath
选项可用于指定备用文件名或目录。例如
./helloworld -XX:HeapDumpPath=$HOME/helloworld.hprof
还要注意:无法在 Microsoft Windows 平台上创建堆转储。
使用 VisualVM 创建堆转储
使用 VisualVM 创建堆转储是一种便捷的方法。为此,您需要将 jvmstat
添加到 --enable-monitoring
选项中(例如,--enable-monitoring=heapdump,jvmstat
)。这将允许 VisualVM 获取和列出正在运行的原生映像进程。然后,您可以以与在 JVM 上运行应用程序时相同的方式请求堆转储(例如,右键单击进程,然后选择“堆转储”。)
在 OutOfMemoryError
上创建堆转储
使用选项 -XX:+HeapDumpOnOutOfMemoryError
启动应用程序,以便在原生可执行文件由于耗尽 Java 堆内存而引发 OutOfMemoryError
时获取堆转储。堆转储创建在名为 svm-heapdump-<PID>-OOME.hprof
的文件中。例如
./mem-leak-example -XX:+HeapDumpOnOutOfMemoryError
Dumping heap to svm-heapdump-67799-OOME.hprof ...
Heap dump file created [10046752 bytes in 0.49 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Garbage-collected heap size exceeded.
转储原生可执行文件的初始堆
使用 -XX:+DumpHeapAndExit
命令行选项转储原生可执行文件的初始堆。这对于识别原生映像构建过程分配给可执行文件堆的对象很有用。对于 HelloWorld 示例,请按如下方式使用该选项
$JAVA_HOME/bin/native-image HelloWorld --enable-monitoring=heapdump
./helloworld -XX:+DumpHeapAndExit
Heap dump created at '/path/to/helloworld.hprof'.
使用 SIGUSR1 创建堆转储(仅限 Linux/macOS)
注意:这需要
Signal
API,该 API 默认启用,但构建共享库时除外。
以下示例是一个简单的多线程 Java 应用程序,它运行 60 秒。这为您提供了足够的时间向它发送 SIGUSR1
信号。该应用程序将处理该信号并在应用程序的工作目录中创建堆转储。堆转储将包含由静态变量 CROWD
引用的 Person
集合。
按照以下步骤构建将在收到 SIGUSR1
信号时生成堆转储的原生可执行文件。
先决条件
确保您已安装 GraalVM JDK。最简单的入门方法是使用 SDKMAN!。有关其他安装选项,请访问 下载部分。
- 将以下代码保存在名为 SVMHeapDump.java 的文件中
import java.nio.charset.Charset; import java.text.DateFormat; import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.Random; import org.graalvm.nativeimage.ProcessProperties; public class SVMHeapDump extends Thread { static Collection<Person> CROWD = new ArrayList<>(); static DateFormat DATE_FORMATTER = DateFormat.getDateTimeInstance(); static int i = 0; static int runs = 60; static int sleepTime = 1000; @Override public void run() { System.out.println(DATE_FORMATTER.format(new Date()) + ": Thread started, it will run for " + runs + " seconds"); while (i < runs) { // Add a new person to the collection CROWD.add(new Person()); System.out.println("Sleeping for " + (runs - i) + " seconds."); try { Thread.sleep(sleepTime); } catch (InterruptedException ie) { System.out.println("Sleep interrupted."); } i++; } } /** * @param args the command line arguments */ public static void main(String[] args) throws InterruptedException { // Add objects to the heap for (int i = 0; i < 1000; i++) { CROWD.add(new Person()); } long pid = ProcessProperties.getProcessID(); StringBuffer sb1 = new StringBuffer(100); sb1.append(DATE_FORMATTER.format(new Date())); sb1.append(": Hello GraalVM native image developer! \n"); sb1.append("The PID of this process is: " + pid + "\n"); sb1.append("Send it a signal: "); sb1.append("'kill -SIGUSR1 " + pid + "' \n"); sb1.append("to dump the heap into the working directory.\n"); sb1.append("Starting thread!"); System.out.println(sb1); SVMHeapDump t = new SVMHeapDump(); t.start(); while (t.isAlive()) { t.join(0); } sb1 = new StringBuffer(100); sb1.append(DATE_FORMATTER.format(new Date())); sb1.append(": Thread finished after: "); sb1.append(i); sb1.append(" iterations."); System.out.println(sb1); } } class Person { private static Random R = new Random(); private String name; private int age; public Person() { byte[] array = new byte[7]; R.nextBytes(array); name = new String(array, Charset.forName("UTF-8")); age = R.nextInt(100); } }
-
构建原生可执行文件
如下编译 SVMHeapDump.java
javac SVMHeapDump.java
使用
--enable-monitoring=heapdump
命令行选项构建原生可执行文件。(这将导致生成的原生可执行文件在收到SIGUSR1
信号时生成堆转储。)native-image SVMHeapDump --enable-monitoring=heapdump
(
native-image
构建器从文件 SVMHeapDump.class 中创建原生可执行文件。当命令完成后,原生可执行文件 svmheapdump 将创建在当前目录中。) -
运行应用程序,向它发送信号,然后检查堆转储
运行应用程序
./svmheapdump 17 May 2022, 16:38:13: Hello GraalVM native image developer! The PID of this process is: 57509 Send it a signal: 'kill -SIGUSR1 57509' to dump the heap into the working directory. Starting thread! 17 May 2022, 16:38:13: Thread started, it will run for 60 seconds
记下 PID 并打开第二个终端。使用 PID 向应用程序发送信号。例如,如果 PID 是
57509
kill -SIGUSR1 57509
堆转储将在应用程序继续运行时创建在工作目录中。堆转储可以使用 VisualVM 工具打开,如下所示。
从原生可执行文件中创建堆转储
以下示例展示了如何在满足某些条件的情况下,使用 VMRuntime.dumpHeap()
从正在运行的原生可执行文件中创建堆转储。创建堆转储的条件作为命令行选项提供。
-
将以下代码保存在名为 SVMHeapDumpAPI.java 的文件中。
import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.nio.charset.Charset; import java.text.DateFormat; import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.Random; import org.graalvm.nativeimage.VMRuntime; public class SVMHeapDumpAPI { static Collection<Person> CROWD = new ArrayList<>(); /** * @param args the command line arguments */ public static void main(String[] args) { // Populate the crowd for (int i = 0; i < 1000; i++) { CROWD.add(new Person()); } StringBuffer sb1 = new StringBuffer(100); sb1.append(DateFormat.getDateTimeInstance().format(new Date())); sb1.append(": Hello GraalVM native image developer. \nYour command line options are: "); if (args.length > 0) { sb1.append(args[0]); System.out.println(sb1); if (args[0].equalsIgnoreCase("--heapdump")) { createHeapDump(); } } else { sb1.append("None"); System.out.println(sb1); } } /** * Create a heap dump and save it into temp file */ private static void createHeapDump() { try { File file = File.createTempFile("SVMHeapDumpAPI-", ".hprof"); VMRuntime.dumpHeap(file.getAbsolutePath(), false); System.out.println(" Heap dump created " + file.getAbsolutePath() + ", size: " + file.length()); } catch (UnsupportedOperationException unsupported) { System.err.println("Heap dump creation failed: " + unsupported.getMessage()); } catch (IOException ioe) { System.err.println("IO went wrong: " + ioe.getMessage()); } } } class Person { private static Random R = new Random(); private String name; private int age; public Person() { byte[] array = new byte[7]; R.nextBytes(array); name = new String(array, Charset.forName("UTF-8")); age = R.nextInt(100); } }
与之前的示例一样,该应用程序创建了一个由静态变量
CROWD
引用的Person
集合。然后,它检查命令行以查看是否需要创建堆转储,然后在createHeapDump()
方法中创建堆转储。 -
构建原生可执行文件。
编译 SVMHeapDumpAPI.java 并构建原生可执行文件
javac SVMHeapDumpAPI.java
native-image SVMHeapDumpAPI
当命令完成后,svmheapdumpapi 原生可执行文件将在当前目录中创建。
-
运行应用程序并检查堆转储
现在,您可以运行原生可执行文件并使用它创建堆转储,输出类似于以下内容
./svmheapdumpapi --heapdump Sep 15, 2020, 4:06:36 PM: Hello GraalVM native image developer. Your command line options are: --heapdump Heap dump created /var/folders/hw/s9d78jts67gdc8cfyq5fjcdm0000gp/T/SVMHeapDump-6437252222863577987.hprof, size: 8051959
然后,可以使用 VisualVM 工具像任何其他 Java 堆转储一样打开生成的堆转储,如下所示。