ネイティブ実行可能ファイルからのヒープ・ダンプの作成
実行中の実行可能ファイルのヒープ・ダンプを作成して、その実行をモニターできます。他の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を使用することです。このためには、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
によって参照される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をコンパイルします:
$JAVA_HOME/bin/javac SVMHeapDump.java
--enable-monitoring=heapdump
コマンドライン・オプションを使用して、ネイティブ実行可能ファイルをビルドします。(これにより、生成されたネイティブ実行可能ファイルがSIGUSR1
シグナルを受信したときにヒープ・ダンプが生成されます。)$JAVA_HOME/bin/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をコンパイルし、ネイティブ実行可能ファイルをビルドします:
$JAVA_HOME/bin/javac SVMHeapDumpAPI.java
$JAVA_HOME/bin/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ツールを使用して開くことができます。