返回

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

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

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

  1. 使用 VisualVM 创建堆转储。
  2. 当原生可执行文件耗尽 Java 堆内存时,可以使用命令行选项 -XX:+HeapDumpOnOutOfMemoryError 来创建堆转储。
  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 上运行应用程序时请求堆转储一样请求堆转储(例如,右键单击进程,然后选择 Heap Dump)。

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 示例,按如下方式使用该选项:

native-image HelloWorld --enable-monitoring=heapdump
./helloworld -XX:+DumpHeapAndExit

堆转储在 path/to/helloworld.hprof 处创建。

使用 SIGUSR1 创建堆转储(仅限 Linux/macOS)

注意:这需要 Signal API,该 API 默认启用,但在构建共享库时除外。

以下示例是一个简单的多线程 Java 应用程序,运行 60 秒。这为您提供了足够的时间发送 SIGUSR1 信号。应用程序将处理该信号并在应用程序的工作目录中创建堆转储。堆转储将包含由静态变量 CROWD 引用的 Persons 的 Collection

请按照以下步骤构建一个原生可执行文件,该文件在收到 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 引用的 Persons 的 Collection。然后它检查命令行以查看是否需要创建堆转储,然后在 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
    

    生成的堆转储可以像任何其他 Java 堆转储一样使用 VisualVM 工具打开,如下所示。

    Native Image Heap Dump View in VisualVM

联系我们