ネイティブ・イメージからのヒープ・ダンプの生成

GraalVM Enterprise Editionを使用すると、ネイティブ・イメージ・プロセスのヒープ・ダンプを生成して実行をモニターできます。

ネイティブ・イメージではJVMTIエージェントが実装されず、VisualVMjmapなどのツールを使用してヒープ・ダンプの作成をトリガーすることはできません。アプリケーションで特定のシグナルを処理できるようにして、USR1シグナルを受信したときにはヒープ・ダンプが取得されるように、アプリケーションのネイティブ・イメージをビルドできます(その他サポートされているシグナルには、スタックダンプを目的としたQUIT/BREAK、ランタイム・コンパイル情報のダンプを目的としたUSR2があります)。GraalVM Enterpriseネイティブ・イメージでイメージをビルドし、-H:+AllowVMInspectionオプションを使用することのみが必要です。

もう1つの可能性は、アプリケーションの存続期間中の特定の時点でヒープ・ダンプを生成する特別なメソッドを記述することです。たとえば、ネイティブ・イメージの実行中に特定の条件が満たされたときに、アプリケーション・コードでヒープ・ダンプの作成をトリガーできます。この目的のために、専用のorg.graalvm.nativeimage.VMRuntime#dumpHeap APIが用意されています。いずれの可能性についてもこのガイドで取り上げます。

ノート: この機能は、GraalVM Enterpriseでのみ使用できます。

SIGUSR1シグナルの処理

次のJavaの例は、60秒間実行される単純なマルチスレッド・アプリケーションです。PIDを取得し、ヒープ・ダンプをアプリケーションの作業ディレクトリに生成するSIGUSR1シグナルを送信するために十分な時間があります。次のコードをSVMHeapDump.javaファイルとしてディスクに保存します:

import java.text.DateFormat;
import java.util.Date;

public class SVMHeapDump extends Thread {
    static int i = 0;
    static int runs = 60;
    static int sleepTime = 1000;
    @Override
    public void run() {
        System.out.println(DateFormat.getDateTimeInstance().format(new Date()) + ": Thread started, it will run for " + runs + " seconds");
        while (i < runs){
            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 {
        StringBuffer sb1 = new StringBuffer(100);
        sb1.append(DateFormat.getDateTimeInstance().format(new Date()));
        sb1.append(": Hello GraalVM native image developer! \nGet PID of this process: ");
        sb1.append("'ps -C svmheapdump -o pid= '\n");
        sb1.append("then send it signal: ");
        sb1.append("'kill -SIGUSR1 <pid_printed_above>' \n");
        sb1.append("to get heap dump generated into 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(DateFormat.getDateTimeInstance().format(new Date()));
        sb1.append(": Thread finished after: ");
        sb1.append(i);
        sb1.append(" iterations.");
        System.out.println(sb1);
    }
}

ネイティブ・イメージのビルド

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

$JAVA_HOME/bin/javac SVMHeapDump.java

これをjavaで実行すると、60秒間実行されてから終了します。

ネイティブ実行可能ファイルをビルドし、ビルダーに-H:+AllowVMInspectionオプションを渡します。これにより、ネイティブ実行可能ファイルでSIGUSR1シグナルが受け入れられて、ヒープ・ダンプが生成されるようになります。

$JAVA_HOME/bin/native-image SVMHeapDump -H:+AllowVMInspection
[svmheapdump:41691]    classlist:     412.03 ms,  2.52 GB
[svmheapdump:41691]        (cap):   1,655.34 ms,  2.52 GB
[svmheapdump:41691]        setup:   2,741.18 ms,  2.52 GB
[svmheapdump:41691]     (clinit):     190.08 ms,  2.59 GB
[svmheapdump:41691]   (typeflow):   5,231.29 ms,  2.59 GB
[svmheapdump:41691]    (objects):   6,489.13 ms,  2.59 GB
[svmheapdump:41691]   (features):     203.11 ms,  2.59 GB
[svmheapdump:41691]     analysis:  12,394.98 ms,  2.59 GB
[svmheapdump:41691]     universe:     425.55 ms,  2.59 GB
[svmheapdump:41691]      (parse):   1,418.69 ms,  2.59 GB
[svmheapdump:41691]     (inline):   1,289.94 ms,  2.59 GB
[svmheapdump:41691]    (compile):  21,338.61 ms,  2.62 GB
[svmheapdump:41691]      compile:  24,795.01 ms,  2.62 GB
[svmheapdump:41691]        image:   1,446.14 ms,  2.62 GB
[svmheapdump:41691]        write:   5,482.12 ms,  2.62 GB
[svmheapdump:41691]      [total]:  47,805.47 ms,  2.62 GB

native-imageビルダーでは、既存のSVMHeapDump.classが分析されて、それに基づいて実行可能ファイルが作成されます。コマンドが完了すると、現在のディレクトリにsvmheapdumpが作成されます。

アプリケーションを実行し、ヒープ・ダンプをチェックします

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

./svmheapdump
May 15, 2020, 4:28:14 PM: Hello GraalVM native image developer!
Get PID of this process: 'ps -C svmheapdump -o pid= '
then send it signal: 'kill -SIGUSR1 <pid_printed_above>'
to get heap dump generated into working directory.
Starting thread!
May 15, 2020, 4:28:14 PM: Thread started, it will run for 60 seconds

2番目の端末を開き、Linux OSの場合はps -C svmheapdump -o pid=、macOSの場合はpgrep svmheapdumpのようなコマンドを使用して、実行中のsvmheapdumpアプリケーションのプロセスIDを取得します。出力されたプロセスID (100など)をコピーし、それを使用して実行中のアプリケーションにシグナルを送信します:

kill -SIGUSR1 100

アプリケーションの実行が継続されている間、作業ディレクトリでヒープ・ダンプを使用できます。

Javaアプリケーション内からのヒープ・ダンプの生成

次のJavaの例は、いくつかの条件が満たされた後に、VMRuntime.dumpHeap()を使用して実行中のJavaアプリケーション内からヒープ・ダンプを生成する方法を示しています。ヒープ・ダンプを生成する条件は、コマンドラインでオプションとして指定します。次のコード・スニペットをSVMHeapDumpAPI.javaとして保存します。

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.text.DateFormat;
import java.util.Date;
import org.graalvm.nativeimage.VMRuntime;

public class SVMHeapDumpAPI {

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        StringBuffer sb1 = new StringBuffer(100);
        sb1.append(DateFormat.getDateTimeInstance().format(new Date()));
        sb1.append(": Hello GraalVM native image developer. \nYour command line options are: ");
        String liveArg = "true";
        if (args.length > 0) {
            sb1.append(args[0]);
            System.out.println(sb1);
            if (args[0].equalsIgnoreCase("--heapdump")){
                if(args.length > 1 ) {
                  liveArg = args[1];
                }
                createHeapDump(Boolean.valueOf(liveArg));
            }
        } else {
            sb1.append("None");
            System.out.println(sb1);
        }
     }

    /**
     * Generate heap dump and save it into temp file
     */
     private static void createHeapDump(boolean live) {
     try {
         File file = File.createTempFile("SVMHeapDump-", ".hprof");
         VMRuntime.dumpHeap(file.getAbsolutePath(), live);
         System.out.println("  Heap dump created " + file.getAbsolutePath() + ", size: " + file.length());
     } catch (UnsupportedOperationException unsupported) {
         System.out.println("  Heap dump creation failed." + unsupported.getMessage());
     } catch (IOException ioe) {
         System.out.println("IO went wrong: " + ioe.getMessage());
     }
 }
}

アプリケーションでは、ダンプ対象の情報を含むデータがいくつか作成され、ヒープ・ダンプを作成する必要があるかどうかがコマンドラインでチェックされます。その後、メソッドcreateHeapDump()メソッドを通じて実際のヒープ・ダンプが作成され、ファイルの存在がチェックされます。

ネイティブ・イメージのビルド

次のステップで、SVMHeapDumpAPI.javaをコンパイルします:

$JAVA_HOME/bin/javac SVMHeapDumpAPI.java

次に、ネイティブ実行可能ファイルをビルドします:

$JAVA_HOME/bin/native-image SVMHeapDumpAPI
[svmheapdumpapi:41691]    classlist:     447.96 ms,  2.53 GB
[svmheapdumpapi:41691]        (cap):   2,105.64 ms,  2.53 GB
[svmheapdumpapi:41691]        setup:   3,010.19 ms,  2.53 GB
[svmheapdumpapi:41691]     (clinit):     178.51 ms,  2.61 GB
[svmheapdumpapi:41691]   (typeflow):   9,153.49 ms,  2.61 GB
[svmheapdumpapi:41691]    (objects):   9,170.40 ms,  2.61 GB
[svmheapdumpapi:41691]   (features):     347.67 ms,  2.61 GB
[svmheapdumpapi:41691]     analysis:  19,208.00 ms,  2.61 GB
[svmheapdumpapi:41691]     universe:     390.40 ms,  2.61 GB
[svmheapdumpapi:41691]      (parse):   1,519.70 ms,  2.63 GB
[svmheapdumpapi:41691]     (inline):   1,072.87 ms,  2.63 GB
[svmheapdumpapi:41691]    (compile):  36,028.90 ms,  2.61 GB
[svmheapdumpapi:41691]      compile:  40,595.67 ms,  2.61 GB
[svmheapdumpapi:41691]        image:   2,384.57 ms,  2.61 GB
[svmheapdumpapi:41691]        write:   3,161.35 ms,  2.63 GB
[svmheapdumpapi:41691]      [total]:  69,300.73 ms,  2.63 GB

コマンドが完了すると、現在のディレクトリに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

生成されたヒープ・ダンプは、他のJavaヒープ・ダンプと同様に、VisualVMツールで開くことができます。