- JAVA
ヒープダンプの取り方と解析|OutOfMemoryErrorの原因を探す
ヒープダンプは、Java のヒープにあるオブジェクトをすべて書き出したファイル(.hprof)です。 本番では、起動オプションに -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=<ディレクトリ> を入れておき、OutOfMemoryError が起きた瞬間のダンプを残すのが基本です。動いているプロセスからは jcmd <PID> GC.heap_dump <ファイル> で取れます。解析では「どのクラスが増えているか」→「どのオブジェクトがそれを抱えているか(保持サイズ)」→「なぜ回収されないか(GC ルートまでの経路)」の順に絞り込みます。
| やりたいこと | 方法 |
|---|---|
| OOM の瞬間に自動で取る | -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/dumps/ |
| 動いているプロセスから取る | jcmd <PID> GC.heap_dump /var/dumps/heap.hprof |
| 小さく取る(JDK 17 以降) | jcmd <PID> GC.heap_dump -gz=1 /var/dumps/heap.hprof.gz |
| ダンプを取らずに増えているクラスを見る | jcmd <PID> GC.class_histogram を時間をおいて2回 |
| リークの元と経路を軽く記録する | JFR の memory-leaks=gc-roots → jfr print --events OldObjectSample |
| ダンプを開いて調べる | Eclipse Memory Analyzer(MAT)・VisualVM |
確認環境:OpenJDK 1.8.0_504・17.0.20.1・21.0.10・25.0.4.1(Ubuntu 24.04)で、消えないキャッシュによるメモリリークを再現し、-XX:+HeapDumpOnOutOfMemoryError・jcmd GC.heap_dump・jmap -dump でダンプを取りました。サイズと時間は JDK 21 で、メモリ上のファイルシステム(tmpfs)に書き出して計測しています。解析は jcmd GC.class_histogram、JFR(jfr print)、VisualVM 2.2 に含まれるヒープ解析ライブラリで行いました。Eclipse MAT の画面と一括処理(ParseHeapDump)は Eclipse の公式ドキュメントで確かめています(確認日 2026年10月13日)。
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「ヒープダンプの取り方と解析|OutOfMemoryErrorの原因を探す」
ヒープダンプでわかること
| わかること | わからないこと |
|---|---|
| クラスごとの個数とサイズ | そのオブジェクトがいつ作られたか(JFR で補う) |
| どのオブジェクトがどれだけのメモリを抱えているか(保持サイズ) | 作ったときのスタック(JFR で補う) |
| GC ルートまでの参照の経路(なぜ回収されないか) | ヒープ以外のメモリ(スレッドスタック・ダイレクトバッファの中身の一部・ネイティブ) |
| フィールドの値(文字列・数値) | CPU の使い方 |
フィールドの値まで入るのが、スレッドダンプとの大きな違いです。これは解析に便利な一方で、個人情報やパスワードがそのまま入ることを意味します(後の章で実際に確かめます)。
OutOfMemoryError のメッセージごとの原因(Java heap space・Metaspace・unable to create native thread など)は、関連記事「Java OutOfMemoryErrorの原因と対処|メッセージ別に解説」で扱っています。この記事は、ヒープダンプの取り方と、ダンプから原因を絞り込む手順に絞ります。
リークを再現するプログラム
ログインのたびに顧客情報をキャッシュに入れ、消さないプログラムです。
import java.util.*;
public class LeakApp {
static class Customer {
final String email; final String name; final byte[] avatar;
Customer(String email, String name, byte[] avatar) { this.email = email; this.name = name; this.avatar = avatar; }
}
// 消されないキャッシュ(リークの原因)
static final Map<String, Customer> SESSION_CACHE = new HashMap<>();
public static void main(String[] args) throws Exception {
long delayMs = args.length > 0 ? Long.parseLong(args[0]) : 0;
int stopAt = args.length > 1 ? Integer.parseInt(args[1]) : Integer.MAX_VALUE;
for (int i = 0; ; i++) {
if (i >= stopAt) { Thread.sleep(1000); continue; }
String id = UUID.randomUUID().toString();
SESSION_CACHE.put(id, new Customer("user" + i + "@example.com", "山田 太郎" + i, new byte[10_000]));
if (i % 1000 == 0) System.out.println("cached=" + SESSION_CACHE.size());
if (delayMs > 0) Thread.sleep(delayMs);
}
}
}
static の Map に入れたものは、remove しない限り GC に回収されません。「static なコレクションに入れっぱなし」は、メモリリークでいちばんよく見る形です。
取り方1:OOM の瞬間に自動で取る
java -Xmx64m \
-XX:+HeapDumpOnOutOfMemoryError \
-XX:HeapDumpPath=/var/dumps/ \
-jar app.jar
上のプログラムを -Xmx64m で動かすと、各 JDK で次のように出ました。
java.lang.OutOfMemoryError: Java heap space
Dumping heap to dumps/java_pid10119.hprof ...
Heap dump file created [69561985 bytes in 0.048 secs]
| JDK | ダンプのサイズ(-Xmx64m) |
書き出し時間(ログの値) |
|---|---|---|
| 8 | 63,125,582 バイト | 0.078 秒 |
| 17 | 68,405,210 バイト | 0.077 秒 |
| 21 | 69,561,985 バイト | 0.048 秒 |
| 25 | 69,980,361 バイト | 0.086 秒 |
ダンプのサイズは、おおよそ「その時点で使っていたヒープ」の大きさになります。-Xmx8g のアプリなら、数 GB のファイルが出ると見込んでディスクを空けておきます。
HeapDumpPath の書き方と挙動
JDK 21・25 で、HeapDumpPath の書き方を変えて試した結果です。
| 指定 | 結果 |
|---|---|
| 指定しない | 作業ディレクトリに java_pid<PID>.hprof |
ディレクトリ(/var/dumps/) |
/var/dumps/java_pid13389.hprof。毎回別名になる |
ファイル名(/var/dumps/heap.hprof) |
2回目以降は Unable to create ...: File exists で書かれない |
| 存在しないディレクトリ | Unable to create ...: No such file or directory で書かれない |
%p を含む(heap-%p.hprof) |
JDK 25 は heap25-13493.hprof と PID に置き換わった。JDK 17・21 は heap-%p.hprof という名前のまま |
ディレクトリを指定し、そのディレクトリを事前に作っておくのが、どの版でも確実です。ファイル名を固定すると、2回目の OOM でダンプが残りません。
一緒に使うオプション
| オプション | 意味 | JDK 21 で確かめた動き |
|---|---|---|
-XX:+ExitOnOutOfMemoryError |
最初の OOM で JVM を終了する | Terminating due to java.lang.OutOfMemoryError: Java heap space、終了コード 3。ダンプは終了前に書かれた |
-XX:+CrashOnOutOfMemoryError |
最初の OOM でクラッシュさせ、エラーログ(hs_err)を残す | ― |
-XX:OnOutOfMemoryError="<コマンド>" |
OOM 時にコマンドを実行する | ― |
-XX:HeapDumpGzipLevel=1 |
OOM 時のダンプを gzip で書く(JDK 17 以降にあるフラグ) | ― |
コンテナで動かすときは、OOM の後にプロセスが動き続けて応答が乱れるのを避けるため、ExitOnOutOfMemoryError を合わせて使い、再起動を Kubernetes などに任せる構成もあります。ダンプの置き場所は、コンテナが消えても残るボリュームにします。
動いているプロセスに後から設定する
HeapDumpOnOutOfMemoryError と HeapDumpPath は、実行中に変えられるフラグ(manageable)です。再起動せずに設定できました(JDK 21)。
jcmd 13700 VM.set_flag HeapDumpOnOutOfMemoryError true
jcmd 13700 VM.set_flag HeapDumpPath /var/dumps/
jcmd 13700 VM.flags -all | grep -E ' HeapDump(OnOut|Path)'
bool HeapDumpOnOutOfMemoryError = true {manageable} {management}
ccstr HeapDumpPath = /var/dumps/ {manageable} {management}
この後に OOM が起きると、/var/dumps/java_pid13700.hprof が作られました。「次に起きたら取りたい」ときに使えます。
取り方2:jcmd GC.heap_dump(動いているプロセスから)
jcmd 12345 GC.heap_dump /var/dumps/heap.hprof
12345:
Dumping heap to /var/dumps/heap.hprof ...
Heap dump file created [317734329 bytes in 0.474 secs]
ファイル名は JVM プロセスから見たパスです。相対パスにすると JVM の作業ディレクトリに書かれるので、絶対パスで書きます。同じ名前のファイルがあると Unable to create ...: File exists になり、JDK 17 以降は -overwrite で上書きできます。
各版の jcmd <PID> help GC.heap_dump で確かめたオプションです。
| オプション | JDK 8 | JDK 17・21 | JDK 25 | 意味 |
|---|---|---|---|---|
-all |
○ | ○ | ○ | 到達できないオブジェクト(ゴミ)も含める。既定は false(生きているものだけ) |
-gz=<1〜9> |
× | ○ | ○ | gzip で書く。help の説明では 1 が推奨(最も速い) |
-overwrite |
× | ○ | ○ | 同名ファイルを上書きする |
-parallel=<数> |
× | × | ○ | 書き出しに使うスレッド数 |
取り方3:jmap -dump
jmap -dump:live,format=b,file=/var/dumps/heap.hprof 12345
jmap は既定が all(ゴミも含む)です。 JDK 25 の jmap -h には「all: dump all objects in the heap (default if one of "live" or "all" is not specified)」とあります。jcmd GC.heap_dump は既定が「生きているものだけ」なので、同じつもりで打つと中身が変わります。
| 項目 | jcmd GC.heap_dump |
jmap -dump |
|---|---|---|
| 既定の範囲 | 生きているオブジェクトだけ | すべて(live を付けると生きているものだけ) |
| ゴミも含める | -all |
既定(または all) |
| 圧縮 | -gz=1 |
gz=1 |
| 生きているものだけにするとき | 既定 | live。先に Full GC が走る |
live を付けると、生きているかどうかを確かめるために Full GC が走ります。一方、ゴミを含めるとファイルが大きくなり、解析ツールでの扱いが重くなります。OOM の調査では、生きているオブジェクトだけを見ることが多いです。ゴミは次の GC で回収されるので、OOM の原因にはなりません。
サイズと停止時間を計測
-Xmx1g で約3万件(約 300MB)を抱えた状態のプロセスから、JDK 21 で取り方を変えて計測しました。-Xlog:safepoint で、JVM が全スレッドを止めていた時間(HeapDumper の安全点)も記録しています。
| 取り方 | ファイルサイズ | ログの書き出し時間 | アプリが止まった時間 |
|---|---|---|---|
jcmd GC.heap_dump |
317,734,329 バイト | 0.474 秒 | 473 ms |
jcmd GC.heap_dump -all |
317,734,329 バイト | 0.327 秒 | 325 ms |
jcmd GC.heap_dump -gz=1 |
8,095,131 バイト | 1.153 秒 | 1,151 ms |
jmap -dump:live |
317,734,122 バイト | 0.533 秒 | 533 ms |
[5.375s][info][safepoint] Safepoint "HeapDumper", Time since last: 4041747237 ns, Reaching safepoint: 4416 ns, Cleanup: 4344 ns, At safepoint: 473004609 ns, Leaving safepoint: 49519 ns, Total: 473062888 ns
読み取れることは次のとおりです。
- ダンプ中は、アプリの全スレッドが止まります。 今回は書き出し時間とほぼ同じ長さ、止まっていました
- 書き出し先の速さで止まる時間が変わります。 今回はメモリ上に書いたため速く、ディスクやネットワークのストレージでは長くなります
-gz=1はファイルは小さくなるが、止まる時間は長くなりました(今回のデータは中身がゼロの配列が多いので、特に縮みやすい例です)- 今回は捨てられたオブジェクトがほとんどない状態だったため、
-allでもサイズは同じでした
数 GB のヒープでは、数秒〜それ以上止まると見込み、ロードバランサーから外してから取る・負荷の低い時間に取るなどの段取りをします。
解析1:クラス別ヒストグラムを2回取って比べる
ヒープダンプを取る前に、まず GC.class_histogram で「何が増えているか」を見ると見当がつきます。ファイルは小さく、すぐ取れます。
jcmd 12222 GC.class_histogram > h1.txt
sleep 10
jcmd 12222 GC.class_histogram > h2.txt
num #instances #bytes class name (module)
-------------------------------------------------------
1: 41942 63379816 [B (java.base@21.0.10)
2: 5295 1045816 [Ljdk.internal.vm.FillerElement; (java.base@21.0.10)
3: 34796 835104 java.lang.String (java.base@21.0.10)
...
9: 6141 147384 LeakApp$Customer
1回だけだと「byte[] が多い」としかわかりません。2回の差を取ると、増え続けているクラスが浮かびます。 差分を出す短いスクリプトです。
import sys, re
def load(p):
d = {}
for line in open(p):
m = re.match(r'\s*\d+:\s+(\d+)\s+(\d+)\s+(\S+)', line)
if m:
d[m.group(3)] = (int(m.group(1)), int(m.group(2)))
return d
a, b = load(sys.argv[1]), load(sys.argv[2])
rows = [(b[k][1] - a.get(k, (0, 0))[1], b[k][0] - a.get(k, (0, 0))[0], k) for k in b]
print(f"{'増えたバイト':>14} {'増えた個数':>10} クラス")
for db, dn, k in sorted(rows, reverse=True)[:8]:
print(f"{db:>14,} {dn:>10,} {k}")
増えたバイト 増えた個数 クラス
47,034,616 18,541 [B
695,296 3,063 [Ljdk.internal.vm.FillerElement;
333,792 13,908 java.lang.String
148,256 4,633 java.util.HashMap$Node
111,192 4,633 LeakApp$Customer
24,576 0 [Ljava.util.HashMap$Node;
LeakApp$Customer と HashMap$Node が同じ数(4,633)だけ増えています。 「Customer が HashMap に入り続けている」と読めます。byte[] が約4倍(18,541)増えているのは、1件ごとに avatar の配列と、メールアドレス・名前・UUID の文字列の中身(String は内部に byte[] を持つ)が増えるためです。
GC.class_histogram は既定で生きているオブジェクトだけを数えるため、取るたびに Full GC が走ります。-all を付けるとゴミも数え、GC は走りません。同じことは jmap -histo:live <PID> でもできます。
解析2:JFR の OldObjectSample で「どこで作られ、何が抱えているか」
JFR(JDK Flight Recorder)には、長く生き残っているオブジェクトを抜き取りで記録する jdk.OldObjectSample というイベントがあります。ヒープダンプを取らずに、リークしているオブジェクトの「作られた場所」と「GC ルートまでの経路」がわかります。
# 起動時に記録を始める(JDK 17 以降の書き方)
java -XX:StartFlightRecording:memory-leaks=gc-roots -jar app.jar
# しばらく動かしてから、経路つきで書き出す
jcmd 12222 JFR.dump name=1 path-to-gc-roots=true filename=/var/dumps/leak.jfr
# 読む
jfr print --events OldObjectSample /var/dumps/leak.jfr
memory-leaks は JDK 21 の default.jfc にある設定で、off・types(既定)・stack-traces・gc-roots から選べます。gc-roots にすると、作ったときのスタックトレースも記録されます。15秒動かした記録には jdk.OldObjectSample が60件入っていました。そのうちの1件です。
jdk.OldObjectSample {
startTime = 14:49:13.903 (2026-10-13)
duration = 259 ms
allocationTime = 14:48:59.565 (2026-10-13)
objectSize = 9.8 kB
objectAge = 14.3 s
lastKnownHeapUsage = 3.1 MB
object = [
byte[10000]
avatar : LeakApp$Customer
value : java.util.HashMap$Node
[1676] : java.util.HashMap$Node[16384]
table : java.util.HashMap Size: 6402
SESSION_CACHE : java.lang.Class Class Name: LeakApp
]
arrayElements = 10000
root = {
description = "Thread Name: main"
system = "Threads"
type = "Stack Variable"
}
eventThread = "main" (javaThreadId = 1)
stackTrace = [
LeakApp.main(String[]) line: 17
]
}
| 項目 | 読み方 |
|---|---|
object |
下から上に読むと、LeakApp.SESSION_CACHE → HashMap(6,402件)→ … → Customer.avatar → byte[10000] という参照の経路 |
objectAge |
作られてから 14.3 秒、生き残っている |
stackTrace |
作られた場所。LeakApp.java の17行目(SESSION_CACHE.put(...) の行) |
duration |
経路を調べるのにかかった時間(259 ms) |
ヒープダンプと違い、ファイルは 259.6 kB で、フィールドの値(メールアドレスなど)は入りません。 本番で個人情報を含むダンプを取りにくいときに向いています。JFR の取り方と読み方の全体は、関連記事「JFR(Java Flight Recorder)入門|記録とjfrコマンド分析」で扱っています。
解析3:ヒープダンプを開いて、保持サイズと GC ルートを見る
ヒストグラムと JFR で見当をつけたら、ヒープダンプで確かめます。解析ツールで見る項目は、どのツールでもほぼ同じです。
| 用語 | 意味(Eclipse MAT のドキュメントより) |
|---|---|
| シャロー(shallow)サイズ | オブジェクト1個そのものが使うメモリ |
| 保持(retained)サイズ | そのオブジェクトが回収されたら、一緒に回収されるオブジェクトの合計 |
| ドミネーターツリー | 保持サイズの大きい順に、「誰が誰を抱えているか」を木で表したもの |
| GC ルートまでの経路 | そのオブジェクトが回収されない理由(どこから参照が続いているか) |
見るべきは保持サイズです。 HashMap 自体のシャローサイズは 48 バイトですが、保持サイズは中身の全部になります。
VisualVM 2.2 に含まれるヒープ解析ライブラリで、OOM の瞬間のダンプ(JDK 21、69,561,985 バイト)を読んだ結果です。読み込みから経路の計算まで約1.2秒でした。
live: 66,217,704 bytes / 88,480 instances
-- retained size by class (top 5)
65,077,632 310 java.util.HashMap
65,069,216 309 java.util.HashMap$Node[]
64,986,968 7,481 java.util.HashMap$Node
64,525,824 34,863 byte[]
64,213,112 6,317 LeakApp$Customer
-- biggest objects by retained size (top 3)
64,986,080 java.util.HashMap#1
64,986,032 java.util.HashMap$Node[]#57
108,456 java.lang.Object[]#564
生きているメモリ 66MB のうち、1つの HashMap(#1)が 65MB を抱えています。 これが「リークの容疑者」です。この HashMap#1 が LeakApp.SESSION_CACHE の値であることも、static フィールドから確かめられました。
-Xmx1g で約3万件を抱えた状態のダンプでは、Customer 1個から GC ルートまでの経路は次のとおりでした。
LeakApp$Customer (shallow 24, retained 10,160)
java.util.HashMap$Node (shallow 32, retained 30,832)
java.util.HashMap$Node[] (shallow 262160, retained 308,654,160)
java.util.HashMap (shallow 48, retained 308,654,208)
java.lang.Class (shallow 72, retained 308,654,400)
java.lang.Class (shallow 72, retained 792)
GC root kind: sticky class
Customer → HashMap → LeakApp クラス(static フィールド)→ GC ルートとつながっており、クラスが読み込まれている限り回収されないことがわかります。直し方は、キャッシュに上限と期限を付ける(Caffeine などのキャッシュライブラリを使う)か、ログアウト時に remove することです。
Eclipse MAT での手順
Eclipse Memory Analyzer(MAT)は、ヒープダンプの解析で最もよく使われる無償のツールです。公式サイトでは、最新版は 1.17.0(2026年6月10日)で、単体版を動かすには Java 21 以上が必要とされています。ドキュメントに沿った手順は次のとおりです。
| 手順 | MAT での操作 | 見るもの |
|---|---|---|
| 1. 開く | File > Open Heap Dump... | 概要(サイズ・クラス数・オブジェクト数) |
| 2. 容疑者を出す | Leak Suspects レポート | 大きな塊を抱えるオブジェクトと、その割合 |
| 3. クラス別に見る | ツールバーのヒストグラム | 個数・シャローサイズ・保持サイズ |
| 4. 抱えている人を見る | ツールバーのドミネーターツリー | 保持サイズの大きいオブジェクトと中身 |
| 5. 理由を見る | 右クリック > Path to GC Roots | 回収されない理由(参照の経路) |
大きなダンプは、画面を使わずにサーバー上でレポートだけ作る方法もあります(ドキュメントの例)。
./mat/ParseHeapDump.sh heap.hprof org.eclipse.mat.api:suspects
org.eclipse.mat.api:overview(概要)と org.eclipse.mat.api:top_components(コンポーネント別)も指定できます。レポートは HTML の zip で出るので、ダンプそのものではなくレポートを共有すると、個人情報を広げずに済みます。
JDK 8 まであった jhat は JDK 9 で削除されており(JEP 241)、今の JDK にはヒープダンプを開くツールは付いていません。
ヒープダンプには個人情報がそのまま入る
OOM のダンプ(69,561,985 バイト)を、ただのバイト列として検索しました。
strings java_pid10119.hprof | grep -m3 '@example.com'
H@example.com
user72@example.com!
user204@example.com!
| 検索した文字列 | 見つかった数 |
|---|---|
@example.com(そのままのバイト列) |
6,319 |
山田 太郎(UTF-16LE のバイト列) |
6,318 |
専用ツールを使わなくても、strings だけでメールアドレスが読めました。 日本語は Java の内部表現(UTF-16)で入っているため strings では見えにくいですが、中身はそのまま残っています。セッションの中身・パスワード・API キー・DB から読んだ行など、メモリにあったものはすべて入ると考えます。
| 対策 | 内容 |
|---|---|
| 権限 | 作られたファイルは -rw-------(所有者だけ読める)だった。コピーするときもこの権限を保つ |
| 置き場所 | 本番サーバーの外に出すときは、社内の取り決めに沿った暗号化ストレージに置く |
| 共有 | ダンプではなく、MAT のレポートやヒストグラム・JFR の結果を共有する |
| 削除 | 解析が終わったら消す。保存期間を決めておく |
| 外部サービス | オンラインの解析サービスに上げるときは、社外にデータを出すことになる点を確認する |
| エンドポイント | Spring Boot Actuator の /actuator/heapdump は、公開範囲を必ず絞る |
Actuator のエンドポイントの公開設定は、関連記事「Spring Boot Actuator設定|エンドポイント一覧と公開設定の雛形」で扱っています。
調査の流れ(まとめ表)
| 段階 | すること | 使うもの |
|---|---|---|
| 備える | OOM 時のダンプ出力を設定し、置き場所の空きを確保する | HeapDumpOnOutOfMemoryError・HeapDumpPath |
| 見当をつける | 増えているクラスを、時間をおいた2回の差で見る | GC.class_histogram |
| 作られた場所を知る | 長生きしているオブジェクトの作成場所と経路を記録する | JFR memory-leaks=gc-roots |
| 確かめる | ダンプで保持サイズの大きいオブジェクトと GC ルートを見る | MAT・VisualVM |
| 直す | 上限・期限のないコレクション、閉じ忘れ、リスナーの登録しっぱなしを直す | コード |
| 再発を見る | GC 後のヒープ使用量が下がるかを監視する | GC ログ・メトリクス |
GC ログでヒープの増え方を見る方法は、関連記事「Java ガベージコレクションの仕組みとGC種類比較|ログの読み方まで」で、-Xmx などの起動オプションは関連記事「JVMオプション一覧|Xmx・Xms・GCログ設定をJava 21対応で」で扱っています。
よくある質問
Q. OOM が起きたのにダンプが作られません。
A. 次の順に確かめます。① HeapDumpPath のディレクトリがあるか(無いと No such file or directory)② ファイル名を固定していて、前のファイルが残っていないか(File exists)③ ディスクの空き ④ OOM がヒープ以外(unable to create native thread など)で、そもそもヒープダンプが役に立つ種類か。JVM の標準出力に Dumping heap to ... の行が出ているかも見ます。
Q. ダンプのサイズが -Xmx よりずっと小さいです。
A. ダンプには、その時点で使っていたオブジェクトだけが入ります。-Xmx64m で OOM になったときのダンプは約 63〜70MB でした。-Xmx よりかなり小さいときは、ヒープ全体ではなく、1回で大きな配列を確保しようとして失敗した可能性があります。Red Hat の JBoss EAP のドキュメントでも、ダンプが -Xmx より小さいときは1つのスレッドが大きく確保した可能性を見るよう書かれています。
Q. 本番で取るときに気をつけることは何ですか。 A. ダンプ中はアプリが止まるので、負荷の低い時間に取るか、ロードバランサーから外してから取ります。ディスクの空きはヒープの使用量以上を確保し、取ったファイルは個人情報として扱います。
Q. ダンプを取らずに調べる方法はありますか。
A. jcmd GC.class_histogram を時間をおいて2回取って比べる方法と、JFR の OldObjectSample を使う方法があります。どちらもフィールドの値は入りません。
まとめ
- 本番では
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=<ディレクトリ>を入れておく。ファイル名を固定すると2回目は書かれない HeapDumpPathの%pは JDK 25 では PID に置き換わったが、JDK 17・21 ではそのままの名前だった- 動いているプロセスからは
jcmd <PID> GC.heap_dump <絶対パス>。jmap -dumpは既定がゴミも含む点が違う - ダンプ中はアプリが止まる。約 300MB のダンプで 0.3〜0.5 秒(メモリ上に書いた場合)。
-gz=1はファイルが小さいが止まる時間は長くなった - 解析は ヒストグラムの差分 → JFR の OldObjectSample → 保持サイズと GC ルートの順に絞る
- ヒープダンプにはメールアドレスなどがそのまま入る。権限・置き場所・削除を決めて扱う
参考・出典
確認日はいずれも 2026年10月13日です。
- JDK 21 Tool Specifications「jcmd」:https://docs.oracle.com/en/java/javase/21/docs/specs/man/jcmd.html
- JDK 21 Tool Specifications「jmap」:https://docs.oracle.com/en/java/javase/21/docs/specs/man/jmap.html
- JDK 21 Tool Specifications「java」:https://docs.oracle.com/en/java/javase/21/docs/specs/man/java.html
- Eclipse Memory Analyzer「Basic Tutorial」:https://help.eclipse.org/latest/topic/org.eclipse.mat.ui.help/gettingstarted/basictutorial.html
- Eclipse Memory Analyzer「Shallow vs. Retained Heap」:https://help.eclipse.org/latest/topic/org.eclipse.mat.ui.help/concepts/shallowretainedheap.html
- Eclipse Memory Analyzer「Running Eclipse Memory Analyzer in batch mode」:https://help.eclipse.org/latest/topic/org.eclipse.mat.ui.help/tasks/batch.html
- Eclipse Memory Analyzer「Download」:https://eclipse.dev/mat/download/
- Red Hat JBoss EAP 7.4 パフォーマンスチューニングガイド「Java ヒープダンプ」:https://docs.redhat.com/ja/documentation/red_hat_jboss_enterprise_application_platform/7.4/html/performance_tuning_guide/heap_dumps
- VisualVM(GitHub リリース):https://github.com/oracle/visualvm/releases
- OpenJDK「JEP 241: Remove the jhat Tool」:https://openjdk.org/jeps/241
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「ヒープダンプの取り方と解析|OutOfMemoryErrorの原因を探す」
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。