ネイティブ実行可能ファイルからのヒープ・ダンプの作成

実行中の実行可能ファイルのヒープ・ダンプを作成して、その実行をモニターできます。他の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が必要です。

次の例は、60秒間実行される単純なマルチスレッドJavaアプリケーションです。これにより、SIGUSR1シグナルを送信するのに十分な時間が得られます。アプリケーションは、シグナルを処理し、アプリケーションの作業ディレクトリにヒープ・ダンプを作成します。ヒープ・ダンプには、静的変数CROWDによって参照されるPersonCollectionが含まれます。

次のステップに従って、SIGUSR1シグナルを受信したときにヒープ・ダンプを生成するネイティブ実行可能ファイルをビルドします。

  1. GraalVM JDKがインストール済であることを確認します。最も簡単に始めるには、SDKMAN!を使用します。その他のインストール・オプションについては、「ダウンロード」セクションを参照してください。

  2. 次のコードを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);
        }
    }
    
  3. ネイティブ実行可能ファイルをビルドします:

    次のようにSVMHeapDump.javaをコンパイルします:

     $JAVA_HOME/bin/javac SVMHeapDump.java
    

    --enable-monitoring=heapdumpコマンドライン・オプションを使用して、ネイティブ実行可能ファイルをビルドします。(これにより、生成されたネイティブ実行可能ファイルがSIGUSR1シグナルを受信したときにヒープ・ダンプが生成されます。)

     $JAVA_HOME/bin/native-image SVMHeapDump --enable-monitoring=heapdump
    

    (native-imageビルダーでは、SVMHeapDump.classからネイティブ実行可能ファイルが作成されます。コマンドが完了すると、現在のディレクトリにネイティブ実行可能ファイルsvmheapdumpが作成されます。)

  4. アプリケーションを実行し、シグナルを送信して、ヒープ・ダンプをチェックします:

    アプリケーションを実行します:

     ./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をメモし、2つ目の端末を開きます。PIDを使用して、シグナルをアプリケーションに送信します。たとえば、PIDが57509の場合:

     kill -SIGUSR1 57509
    

    アプリケーションの実行が継続されている間、作業ディレクトリにヒープ・ダンプが作成されます。ヒープ・ダンプは、次に示すように、VisualVMツールを使用して開くことができます。

    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によって参照されるPersonCollectionを作成します。次に、コマンドラインをチェックしてヒープ・ダンプを作成する必要があるかどうかを確認し、メソッドcreateHeapDump()でヒープ・ダンプを作成します。

  2. ネイティブ実行可能ファイルをビルドします

    SVMHeapDumpAPI.javaをコンパイルし、ネイティブ実行可能ファイルをビルドします:

     $JAVA_HOME/bin/javac SVMHeapDumpAPI.java
    
     $JAVA_HOME/bin/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ツールを使用して開くことができます。

    VisualVMのネイティブ・イメージ・ヒープ・ダンプ・ビュー