返回

从原生可执行文件创建堆转储

您可以创建正在运行的可执行文件的堆转储以监控其执行。就像任何其他 Java 堆转储一样,它可以使用 VisualVM 工具打开。

要启用堆转储支持,原生可执行文件必须使用 --enable-monitoring=heapdump 选项构建。然后可以通过以下方式创建堆转储

  1. 使用 VisualVM 创建堆转储。
  2. 命令行选项 -XX:+HeapDumpOnOutOfMemoryError 可用于在原生可执行文件耗尽 Java 堆内存时创建堆转储。
  3. 使用 -XX:+DumpHeapAndExit 命令行选项转储原生可执行文件的初始堆。
  4. 在运行时向应用程序发送 SIGUSR1 信号来创建堆转储。
  5. 使用 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!。有关其他安装选项,请访问 下载部分

  1. 将以下代码保存在名为 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);
        }
    }
    
  2. 构建原生可执行文件

    如下编译 SVMHeapDump.java

     javac SVMHeapDump.java
    

    使用 --enable-monitoring=heapdump 命令行选项构建原生可执行文件。(这将导致生成的原生可执行文件在收到 SIGUSR1 信号时生成堆转储。)

     native-image SVMHeapDump --enable-monitoring=heapdump
    

    native-image 构建器从文件 SVMHeapDump.class 中创建原生可执行文件。当命令完成后,原生可执行文件 svmheapdump 将创建在当前目录中。)

  3. 运行应用程序,向它发送信号,然后检查堆转储

    运行应用程序

     ./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 工具打开,如下所示。

    Native Image Heap Dump View in VisualVM

从原生可执行文件中创建堆转储

以下示例展示了如何在满足某些条件的情况下,使用 VMRuntime.dumpHeap() 从正在运行的原生可执行文件中创建堆转储。创建堆转储的条件作为命令行选项提供。

  1. 将以下代码保存在名为 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() 方法中创建堆转储。

  2. 构建原生可执行文件。

    编译 SVMHeapDumpAPI.java 并构建原生可执行文件

     javac SVMHeapDumpAPI.java
    
     native-image SVMHeapDumpAPI
    

    当命令完成后,svmheapdumpapi 原生可执行文件将在当前目录中创建。

  3. 运行应用程序并检查堆转储

    现在,您可以运行原生可执行文件并使用它创建堆转储,输出类似于以下内容

     ./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 堆转储一样打开生成的堆转储,如下所示。

    Native Image Heap Dump View in VisualVM

与我们联系