1. jmap: コマンドラインの魔法
自動ガベージコレクタはもちろん素晴らしい。しかし、Java アプリケーションでさえ「リーク」することがあります(OutOfMemoryError)、遅くなったり、メモリの誤用で予期せずカクつくこともあります。原因はさまざまです:
- メモリリーク(memory leaks):本来破棄されるべきオブジェクトが生き続ける。
- コレクションに過大なデータ量。
- キャッシュロジックの不備。
- 未解放のリソース(例:スレッド、ファイル)。
あなたの課題は、こうした問題を素早く見つけられるようになること。そうでないと、プログラマではなく「バグの開発者」になってしまうかもしれません!
jmap とは?
jmap は、JDK 標準のユーティリティです。稼働中の Java プロセスのメモリの「スナップショット」(heap dump)を取得できます。このダンプを他のツールで開き、どのオブジェクトがどれだけメモリを占有しているか、個数、参照元などを確認できます。
ヒープダンプは、ヒープの写真のようなものです:中にあるすべてのオブジェクト、その型、参照関係、サイズが写っています。
jmap の使い方
プロセスの PID を調べる
まず、あなたの Java アプリケーションが動作しているプロセスの識別子(PID)を調べます。方法はいくつかあります:
jps(JDK に含まれる別のユーティリティ)を使う:
jps -l
Java プロセスとその PID の一覧が表示されます。
タスク マネージャー(Task Manager)や Linux の ps でも可。
ヒープダンプを取得する
jmap -dump:format=b,file=heap.bin <PID>
- format=b — バイナリ形式(解析に適しています)。
- file=heap.bin — ダンプを書き出すファイル名。
- <PID> — プロセス ID。
例:
jmap -dump:format=b,file=heap.bin 12345
取得したヒープダンプはどう使う?
通常、ダンプはグラフィカルツール(たとえば jvisualvm や Eclipse MAT)で解析します。バイナリを手作業で追うのは達人の領域で、少しマゾヒズムです。
jmap のその他の機能
メモリ統計の表示:
jmap -heap <PID>
ヒープの情報(サイズ、使用されている GC、状態)を表示します。
クラス一覧:
jmap -histo <PID>
クラスごとの統計(どの型のオブジェクトが何個あり、合計サイズはいくつか)を表示します。
出力例:
| # | オブジェクト | 件数 | バイト |
|---|---|---|---|
| 1 | |
1200 | 48000 |
| 2 | |
1000 | 32000 |
| 3 | |
200 | 12800 |
これだけでも傾向が分かります。特定の型のオブジェクトが突如何百万件もあるなら、要注意です。
2. jvisualvm: メモリの可視化解析ツール
jvisualvm は JDK に同梱されたグラフィカルアプリです(bin フォルダを確認)。ローカル(場合によってはリモート)の Java プロセスに接続し、リアルタイムで監視し、heap dump を取得し、メモリ・スレッド・GC・CPU の統計を見たり、コード実行をプロファイルできます。
マウスで操作して綺麗なグラフを眺めるのが好きなら、これはあなたのツールです。
jvisualvm の起動方法
コマンドライン(または Windows のショートカット)から:
jvisualvm
ローカルの Java プロセス一覧が表示されるウィンドウが開きます。
jvisualvm の主な機能
リアルタイムのメモリ監視
- 左側の一覧からプロセスを選択。
- Monitor タブへ移動。
- ヒープ、CPU、スレッド数、クラス数のグラフが見られます。
例:

Heap Dump(メモリスナップショット)
- Monitor パネルで Heap Dump を押す。
- 数秒後、ダンプ解析のタブが表示されます。
- すべてのクラス、オブジェクト数、合計サイズが一覧で見られます。
「重い」オブジェクトの特定
- サイズや個数でソートする。
- ArrayList や String など特定の型がメモリを占有しすぎているなら、調査対象です。
参照関係の分析(Reference Graph)
- クラスをクリックすると、そのクラスのオブジェクトを誰が参照しているかが分かります。
- なぜ GC に回収されないのか、ルートまで辿れます。
スレッドの分析
- Threads タブはすべてのスレッドとその状態を表示します。
- ハングしている、あるいは過剰に活動しているスレッドを検出できます。
プロファイリング
- Profiler タブでは、時間やメモリを最も消費しているメソッドを測定できます。
- より深い分析ですが、メモリリークの探索には最初の手順だけでも十分です。
3. 実践: メモリリークを解析する
リークのあるコード例
import java.util.ArrayList;
import java.util.List;
public class MemoryLeakDemo {
// 静的コレクション — ありがちなパターン!
private static final List<String> bigList = new ArrayList<>();
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 1_000_000; i++) {
bigList.add("行番号 " + i);
if (i % 100_000 == 0) {
System.out.println("追加済み: " + i);
Thread.sleep(500); // 解析のための時間を与える
}
}
System.out.println("完了! アプリを終了せずに jvisualvm を開いてください。");
Thread.sleep(600_000); // 解析のための 10 分
}
}
何が起きているのか?
- 静的なリストを作成し、そこに 100 万件の文字列を追加しています。
- 追加が終わると、ツールで接続できるようにプログラムは待機(いわば「停止」)します。
jvisualvm での分析
- プログラムを起動する。
- jvisualvm を開き、プロセスを選ぶ。
- Monitor タブでメモリグラフを確認する。
- 問題がなければ、GC 後にメモリは解放されます。
- リークがある場合、メモリは増え続けます。
- Heap Dump を取得する。
- どのオブジェクトが最もメモリを消費しているかを確認する。
- この例では、java.util.ArrayList と java.lang.String です。
- オブジェクトをクリックして、誰がそれを参照しているかを見る。
- 静的フィールド bigList が保持していることが分かります。
どう直す?
- 不要な要素はコレクションから削除し、増大を抑える。
- 不要になった重いオブジェクトへの参照は null にする。
- 常時不要なら、大きなコレクションを static に保持しない。
4. その他のツール: Eclipse MAT、jconsole
Eclipse Memory Analyzer (MAT)
Eclipse MAT は無料かつ強力な heap dump 解析ツールです。どのオブジェクトがメモリを占有しているか、どれが他を保持しているか——いわゆる retained set——を可視化できます。これにより、リークの所在を正確に突き止められます。
疑わしいオブジェクト(leak suspects)に関する詳細レポートを生成でき、数ギガバイト級の巨大ダンプでも安定して扱えます。
使い方は簡単です:jmap や jvisualvm でヒープダンプを取得し、MAT で開いて Leak Suspects Report を押すだけ。潜在的な問題がハイライトされたレポートが得られます。
jconsole
jconsole は、JVM をリアルタイム監視するためのシンプルで使いやすいツールです。使用メモリ量、CPU 負荷、稼働スレッド数、GC の挙動などを表示します。
VisualVM のような高機能ツールと違い、jconsole は heap dump を解析しませんが、素早い診断には最適です——アプリ内部で今何が起きているかをひと目で把握できます。
5. メモリ解析での典型的なミス
ミス 1: 別のプロセスのダンプを取ってしまう。 1 台で多くの Java アプリが動いていることはよくあり、PID を取り違えることがあります。jps で確認し、自分のプログラムを解析していることを必ずチェックしましょう。
ミス 2: ないところに「リーク」を期待してしまう。 GC は即座にオブジェクトを回収しないことがあります——メモリが「解放されたように見える」のは Full GC の後だけ、というケースも。1 回の GC でメモリが減らなくても慌てず、トレンドを見ましょう。
ミス 3: static/キャッシュされたオブジェクトの影響を見落とす。 多くのリークは、static フィールドやグローバルコレクションにオブジェクトを保持していることが原因です。「重い」オブジェクトを誰が参照しているかを確認しましょう——たいていは static です。
ミス 4: 時系列でのプロファイリングを使わない。 heap dump は有用ですが、ときにはメモリの時間的な増加(jvisualvm のグラフ)を見ることが重要です。メモリが安定して増え続けるなら、調査すべきサインです。
ミス 5: リスナー/イベントハンドラを解除しない。 イベントに購読したまま解除しないと、オブジェクトは使っていなくてもメモリに残り続けます。
アドバイス: ツールを恐れず試してみましょう! ダンプを取得し、グラフを眺め、オブジェクトをクリックする——そうしてはじめて、あなたのプログラムのメモリ感覚が身につき、問題を素早く見つけられるようになります。
GO TO FULL VERSION