Java ヒープ ダンプのキャプチャと分析

Mehvish Ashiq 2023年12月11日
  1. ヒープ ダンプとその形式の概要
  2. Java で OutOfMemoryError を引き起こすコード例
  3. ヒープ ダンプをキャプチャするさまざまな方法
  4. Java ヒープ ダンプの分析
Java ヒープ ダンプのキャプチャと分析

ヒープ ダンプには、実行中の Java アプリケーションが Java ヒープで使用するすべてのライブ オブジェクトのスナップショットが含まれます。 このチュートリアルでは、ヒープ ダンプ、そのさまざまな形式、およびその重要性について説明します。

さらに、OutOfMemoryError を示す例を見ていきます。これは、ヒープ ダンプをキャプチャするためのさまざまなアプローチとそれを分析するためのツールにつながります。

ヒープ ダンプとその形式の概要

ヒープには、クラスをインスタンス化して作成したすべてのオブジェクトが含まれています。 Java ランタイムのすべてのクラスもこのヒープに作成されます。

このヒープは、JVM (Java 仮想マシン) の開始時に作成され、アプリケーションで破棄または作成されたオブジェクトを調整するために実行時に拡張/縮小できます。

ガベージ コレクション プロセスは、ヒープがいっぱいになるたびに実行されます。このプロセスは、使用されなくなったオブジェクト、または参照されなくなったオブジェクトをすべて収集します (メモリ管理の詳細については こちら を参照してください)。通常、ヒープ ダンプは バイナリ形式の hprof ファイル。

タイプ、クラス名、アドレス、サイズ、インスタンスに別のオブジェクトへの参照が含まれているかどうかなど、各オブジェクト インスタンスに関する詳細情報を取得できます。 ヒープ ダンプは、次の 2つの形式のいずれかになります。

  1. ポータブル ヒープ ダンプ形式 (PHD 形式とも呼ばれます)
  2. クラシックフォーマット

ポータブル ヒープ ダンプはデフォルトの形式であり、さらに分析するために処理する必要があるバイナリであることに注意してください。 一方、古典的な形式は人間が読めるASCIIテキストです。

これら 2つの形式については こちら を参照してください。

ヒープダンプを使用する重要性

通常、アプリケーションが OutOfMemoryError によってクラッシュした場合、または Java アプリケーションが予想よりも多くのメモリを消費した場合に、ヒープ ダンプの利点が得られます。

ヒープ ダンプは、エラーの主な原因とその他の詳細 (たとえば、すべてのクラスのオブジェクト数、すべてのクラスのメモリ使用量など) を特定するのに役立ちます。

また、アプリケーションの各 Java オブジェクトが占有するメモリ サイズを取得するのにも役立ちます。 このキャプチャされたすべての情報は、メモリ リークの問題を引き起こしている実際のコードを見つけるのに役立ちます。

OutOfMemoryError を引き起こすコード例を見てみましょう。これは、Java ヒープ ダンプをキャプチャするさまざまな方法につながります。

Java で OutOfMemoryError を引き起こすコード例

import java.util.ArrayList;
import java.util.List;

public class Test {
  public static void main(String[] args) {
    List<byte[]> myList = new ArrayList<>();
    int index = 0;

    while (true) {
      // 1MB each iteration, 1 x 1024 x 1024 = 1048576
      byte[] bytes = new byte[1048576];
      myList.add(bytes);
      Runtime runTime = Runtime.getRuntime();
      System.out.printf("[%d] free memory is: %s%n", index++, runTime.freeMemory());
    }
  }
}

上記のコードは、Java 仮想マシンが十分なメモリを割り当てられない特定のポイントに到達するまで、while ループを実行してメモリを割り当て続けます。

その時点で、java.lang.OutOfMemoryError: Java heap space エラーが発生します。

...
...
...
[1510] free memory is: 14687728
[1511] free memory is: 12590576
[1512] free memory is: 10493424
[1513] free memory is: 8396272
[1514] free memory is: 6299120
[1515] free memory is: 4201968
[1516] free memory is: 2104320
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
        at Test.main(Test.java:16)

ここで、ヒープ ダンプ分析を実行して、OutOfMemoryError の原因を特定する必要があります。 それは2つのステップで行うことができます。

まず、ヒープ ダンプをキャプチャし、次にヒープ ダンプ ファイルを分析して、疑わしい理由を見つけます。

ヒープ ダンプをキャプチャするさまざまな方法

ヒープ ダンプをキャプチャする方法は複数あります。 以下、一つ一つ学んでいきましょう。

  1. jmap
  2. JVisualVM
  3. jcmd
  4. ヒープダンプを自動生成する
  5. JMX

jmap を使用してヒープ ダンプをキャプチャする

jmap ツールは、実行中の Java 仮想マシン (JVM) のメモリ統計を出力します。 リモートおよびローカルプロセスにも使用できます。

-dump オプションを使用して、JDK のホーム ディレクトリの bin フォルダーから使用できる jmap ツールを使用してヒープ ダンプをキャプチャします。

構文:

jmap -dump:[live],format=b,file=<file-path> <pid>

例:

C:\Program Files\Java\jdk-18\bin> jmap -dump:live,format=b,file=/temp/dump.hprof 12876

以下は、上記で指定したオプションの簡単な説明です。

パラメータ 説明
live このパラメーターはオプションです。 設定されている場合、アクティブな参照を持つオブジェクトのみが印刷されます。 ガベージ コレクションの準備ができているものは省略されます。
format=b これは、ダンプ ファイルを保存する形式を指定するために使用されます。 ここでは b を使用しました。これは、このダンプ ファイルがバイナリ形式であることを意味します。 このパラメータが設定されていない場合、結果は同じになります。
file これは、ダンプが書き込まれる file です。
pid Java プロセスの ID を示します。

pid を取得するには、jps コマンドを使用できることに注意してください。 さらに、jmap は JDK で実験的なツールとして導入されましたが、サポートされていません。 したがって、状況によっては、jmap を使用する代わりに他のツールを使用する必要がある場合があります。

JVisualVM を使用してヒープ ダンプをキャプチャする

JVisualVM は、Java アプリケーションのプロファイリングとトラブルシューティングを追跡できるグラフィカル ユーザー インターフェイスです。 シンプルで使いやすく、ヒープ ダンプを取得できます。

this によると、Oracle JDK 6、7、および 8 で利用可能でした。JDK 9 以降では、JVisualVM は Oracle JDK では提供されなくなりました。 ユーザーは、使用する場合は個別にダウンロードする必要があります。

visualvm.github.io からダウンロードして、.zip ファイルを解凍し、bin フォルダーで visualvm.exe を見つけてダブルクリックし、プロンプトが表示されたら I Accept ボタンをクリックします。 次の画面が表示されます。

Java ヒープ ダンプのキャプチャと分析 - visualvm 起動ウィンドウ

現在実行中のすべての Java プロセスは、Local の下に一覧表示されます。 目的の Java プロセスを選択して右クリックし、[ヒープ ダンプ] オプションを選択すると、ヒープ ダンプを取得できます。

必要なすべての情報を示す新しいタブが開きます。

jcmd を使用してヒープ ダンプをキャプチャする

このツールは、JDK のホーム ディレクトリの bin フォルダーにもあります。 私たちの場合は、C:\Program Files\Java\jdk-18\bin です。 デフォルトの場所に Java をインストールしていない場合は、異なる可能性があります。

jcmd は、コマンド要求を Java 仮想マシンに送信します。 Java プロセスを実行している同じマシンで使用する必要があることに注意してください。

複数のコマンドがあり、そのうちの 1つが GC.heap-dump で、プロセス ID (pid) と出力ファイルのパスを指定してヒープ ダンプを取得するために使用します。 以下の jcmd コマンドの構文を参照してください。

構文:

jcmd <pid> GC.head_dump <file-path>

例:

C:\Program Files\Java\jdk-18\bin> jcmd 12876 GC.head_dump /temp/dump.hprof

jmap と同様に、ダンプもバイナリ形式で生成されます。

ヒープ ダンプを自動的にキャプチャする

私たちが学んだすべてのアプローチは、特定の時間に手動でヒープ ダンプを取得しますが、状況によっては、java.lang.OutOfMemoryError が発生するとすぐにヒープ ダンプを生成する必要があります。

ここで、ヒープ ダンプを自動的に生成すると、エラーの調査に役立ちます。

これらのシナリオを考慮して、Java は、アプリケーションが java.lang.OutOfMemoryError をスローしたときにヒープ ダンプを生成できるコマンド ライン オプションである HeapDumpOnOutOfMemoryError を提供します。

java -XX:+HeapDumpOnOutOfMemoryError

デフォルトでは、上記のコマンドは、アプリケーションが実行されている場所にある java_pid<pid>.hprof ファイルにダンプを保存します。 カスタム ディレクトリまたはファイルを指定して、それを HeapDumpPath オプションで設定できます。

構文:

java -XX:+HeapDumpOnOutOfMemoryError  -XX:HeapDumpPath=<file-or-dir-path>

例:

java -XX:+HeapDumpOnOutOfMemoryError  -XX:HeapDumpPath=/temp/heapdump.bin

これで、このオプションを使用してアプリケーションがメモリ不足になるたびに、次のようにログでヒープ ダンプを含む作成されたファイルを見つけることができます。

java.lang.OutOfMemoryError: Requested array size exceeds VM limit
During heap to java_pid12876.hprof...
  Exception in thread "main" Head dump file created [4745371 bytes in 0.028 secs]
  java.lang.OutOfMemoryError: Requested array size exceeds VM limit

上記のテキストが java_pid12876.hprof ファイルに書き込まれていることがわかります。このオプションを使用してアプリケーションを実行している間、オーバーヘッドはありません。

OutOfMemoryError がいつ発生するかわからないため、すべてのアプリケーション、特に本番環境でこのオプションを使用することをお勧めします。

HotSpotDiagnostic MBean を使用して実行時にこのオプションを使用できることを思い出してください。 そのために、JConsole を使用し、HeapDumpOnOutOfMemoryError VM オプションを true に設定します。

JMX を使用してヒープ ダンプをキャプチャする

このメソッドでは、次の 2つのパラメーターを受け入れる dumpHeap メソッドを提供する HotSpotDiagnostic MBean を使用します。

パラメータ 説明
outputFile ダンプの出力ファイルのパスです。 このファイルには、ダンプを保持するために .hprof 拡張子が必要です。
live true に設定すると、このチュートリアルで jmap を使用して学んだように、メモリ内のアクティブなオブジェクトのみがダンプされます。

ヒープ ダンプをキャプチャする、プログラムで呼び出す、または JDK のホーム ディレクトリの bin フォルダーにある JConsole のような JMX クライアントを使用する 2つの方法で呼び出すことができます。 ここでは JMX を使用しますが、プログラムで呼び出す方法については こちら を参照してください。

JMX クライアント (JConsole) 経由で HotSpotDiagnostic MBean を使用するのが、JConsole を開き、実行中の Java プロセスに接続し、MBeans タブに移動し、com の下で HotSpotDiagnostic を探す最も簡単な方法です。 .sun.management.

Operations ドロップダウンの下に dumpHeap メソッドがあります。ここで、outputFile および live パラメータを p0 および p1 テキスト フィールドに指定して、以下に示すように dumpHeap 操作を実行できます。

Java ヒープ ダンプのキャプチャと分析 - mbeans

ここで、Java ヒープ ダンプを分析します。

Java ヒープ ダンプの分析

Java ヒープ ダンプでは、高メモリを使用しているオブジェクトメモリを解放していないオブジェクトを見つけるためのオブジェクト グラフ、および到達可能オブジェクトと到達不能オブジェクトを探す必要があります。

Eclipse Memory Analyzer (MAT) は、先ほど生成した Java ヒープ ダンプを分析するのに最適です。 これを行うために、Memory Analyzer Tool を開始し、ヒープ ダンプ ファイルを開きます。

Eclipse メモリ アナライザー (MAT) には、以下で簡単に説明する 2 種類のオブジェクト サイズがあります。

  1. 浅いヒープ サイズ - オブジェクトの浅いヒープは、メモリ内のサイズです。
  2. Retained Heap Size - オブジェクトがガベージ コレクションされると解放されるメモリの量です。

ヒープ ダンプ ファイルを開くと、アプリケーションのメモリ使用量の概要を確認できます。 これで、OutOfMemoryError の原因を簡単に突き止めることができます。

著者: Mehvish Ashiq
Mehvish Ashiq avatar Mehvish Ashiq avatar

Mehvish Ashiq is a former Java Programmer and a Data Science enthusiast who leverages her expertise to help others to learn and grow by creating interesting, useful, and reader-friendly content in Computer Programming, Data Science, and Technology.

LinkedIn GitHub Facebook

関連記事 - Java Heap