◀戻る
ネイティブ実行ファイルからのヒープダンプの作成
実行中の実行ファイルの実行状況を監視するために、ヒープダンプを作成できます。他のJavaヒープダンプと同様に、VisualVMツールで開くことができます。
ヒープダンプのサポートを有効にするには、ネイティブ実行ファイルを--enable-monitoring=heapdump
オプションを付けてビルドする必要があります。ヒープダンプは、以下の方法で作成できます。
- VisualVMでヒープダンプを作成します。
- コマンドラインオプション
-XX:+HeapDumpOnOutOfMemoryError
を使用すると、ネイティブ実行ファイルがJavaヒープメモリを使い果たしたときにヒープダンプを作成できます。 -XX:+DumpHeapAndExit
コマンドラインオプションを使用して、ネイティブ実行ファイルの初期ヒープをダンプします。- 実行時にアプリケーションに
SIGUSR1
シグナルを送信して、ヒープダンプを作成します。 org.graalvm.nativeimage.VMRuntime#dumpHeap
APIを使用して、プログラムでヒープダンプを作成します。
すべてのアプローチについて、以下で説明します。
注:デフォルトでは、ヒープダンプは現在の作業ディレクトリに作成されます。
-XX:HeapDumpPath
オプションを使用して、代替のファイル名またはディレクトリを指定できます。例えば
./helloworld -XX:HeapDumpPath=$HOME/helloworld.hprof
注:Microsoft Windowsプラットフォームでは、ヒープダンプを作成できません。
VisualVMでヒープダンプを作成する
ヒープダンプを作成する便利な方法は、VisualVMを使用することです。このためには、--enable-monitoring
オプションにjvmstat
を追加する必要があります(例:--enable-monitoring=heapdump,jvmstat
)。これにより、VisualVMは実行中のネイティブイメージプロセスを検出してリストできます。その後、アプリケーションがJVMで実行されている場合と同じ方法でヒープダンプをリクエストできます(例:プロセスを右クリックし、[ヒープダンプ]を選択します)。
OutOfMemoryError
でヒープダンプを作成する
ネイティブ実行ファイルがJavaヒープメモリを使い果たしたためにOutOfMemoryError
をスローした場合にヒープダンプを取得するには、-XX:+HeapDumpOnOutOfMemoryError
オプションを指定してアプリケーションを起動します。ヒープダンプは、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
によって参照されるPerson
のCollection
が含まれます。
SIGUSR1
シグナルを受信したときにヒープダンプを生成するネイティブ実行ファイルをビルドするには、次の手順に従います。
前提条件
GraalVM JDKがインストールされていることを確認してください。開始する最も簡単な方法は、SDKMAN!を使用することです。その他のインストールオプションについては、ダウンロードセクションをご覧ください。
- 次のコードを*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); } }
-
ネイティブ実行ファイルをビルドする
*SVMHeapDump.java*を次のようにコンパイルします。
javac SVMHeapDump.java
--enable-monitoring=heapdump
コマンドラインオプションを使用して、ネイティブ実行ファイルをビルドします。(これにより、結果のネイティブ実行ファイルは、SIGUSR1
シグナルを受信したときにヒープダンプを生成します。)native-image SVMHeapDump --enable-monitoring=heapdump
(
native-image
ビルダーは、*SVMHeapDump.class*ファイルからネイティブ実行ファイルを作成します。コマンドが完了すると、ネイティブ実行ファイル*svmheapdump*が現在のディレクトリに作成されます。) -
アプリケーションを実行し、シグナルを送信して、ヒープダンプを確認します
アプリケーションを実行する
./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ツールで開くことができます。
ネイティブ実行ファイル内からヒープダンプを作成する
次の例は、何らかの条件が満たされた場合に、VMRuntime.dumpHeap()
を使用して、実行中のネイティブ実行ファイルからヒープダンプを作成する方法を示しています。ヒープダンプを作成するための条件は、コマンドラインでオプションとして指定されます。
-
以下のコードを*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()
でヒープダンプを作成します。 -
ネイティブ実行ファイルをビルドします。
*SVMHeapDumpAPI.java*をコンパイルし、ネイティブ実行ファイルをビルドします
javac SVMHeapDumpAPI.java
native-image SVMHeapDumpAPI
コマンドが完了すると、*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ツールで開くことができます。