返回

從原生執行檔建立堆積傾印

您可以建立正在執行的執行檔的堆積傾印,以監控其執行狀況。就像任何其他 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 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 參考的 Person 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
    

    產生的堆積傾印然後可以使用 VisualVM 工具開啟,就像任何其他 Java 堆積傾印一樣,如下所示。

    Native Image Heap Dump View in VisualVM

與我們聯繫