CodeGym /コース /JAVA 25 SELF /メモリ解析ツール:jmap、jvisualvm

メモリ解析ツール:jmap、jvisualvm

JAVA 25 SELF
レベル 64 , レッスン 3
使用可能

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

取得したヒープダンプはどう使う?
通常、ダンプはグラフィカルツール(たとえば jvisualvmEclipse MAT)で解析します。バイナリを手作業で追うのは達人の領域で、少しマゾヒズムです。

jmap のその他の機能

メモリ統計の表示:

jmap -heap <PID>

ヒープの情報(サイズ、使用されている GC、状態)を表示します。

クラス一覧:

jmap -histo <PID>

クラスごとの統計(どの型のオブジェクトが何個あり、合計サイズはいくつか)を表示します。

出力例:

# オブジェクト 件数 バイト
1
[C
1200 48000
2
java.lang.String
1000 32000
3
java.util.HashMap
200 12800

これだけでも傾向が分かります。特定の型のオブジェクトが突如何百万件もあるなら、要注意です。

2. jvisualvm: メモリの可視化解析ツール

jvisualvm は JDK に同梱されたグラフィカルアプリです(bin フォルダを確認)。ローカル(場合によってはリモート)の Java プロセスに接続し、リアルタイムで監視し、heap dump を取得し、メモリ・スレッド・GC・CPU の統計を見たり、コード実行をプロファイルできます。

マウスで操作して綺麗なグラフを眺めるのが好きなら、これはあなたのツールです。

jvisualvm の起動方法

コマンドライン(または Windows のショートカット)から:

jvisualvm

ローカルの Java プロセス一覧が表示されるウィンドウが開きます。

jvisualvm の主な機能

リアルタイムのメモリ監視

  • 左側の一覧からプロセスを選択。
  • Monitor タブへ移動。
  • ヒープ、CPU、スレッド数、クラス数のグラフが見られます。

例:

jvisualvm-monitor

Heap Dump(メモリスナップショット)

  • Monitor パネルで Heap Dump を押す。
  • 数秒後、ダンプ解析のタブが表示されます。
  • すべてのクラス、オブジェクト数、合計サイズが一覧で見られます。

「重い」オブジェクトの特定

  • サイズや個数でソートする。
  • ArrayListString など特定の型がメモリを占有しすぎているなら、調査対象です。

参照関係の分析(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 での分析

  1. プログラムを起動する。
  2. jvisualvm を開き、プロセスを選ぶ。
  3. Monitor タブでメモリグラフを確認する。
    • 問題がなければ、GC 後にメモリは解放されます。
    • リークがある場合、メモリは増え続けます。
  4. Heap Dump を取得する。
  5. どのオブジェクトが最もメモリを消費しているかを確認する。
    • この例では、java.util.ArrayListjava.lang.String です。
  6. オブジェクトをクリックして、誰がそれを参照しているかを見る。
    • 静的フィールド bigList が保持していることが分かります。

どう直す?

  • 不要な要素はコレクションから削除し、増大を抑える。
  • 不要になった重いオブジェクトへの参照は null にする。
  • 常時不要なら、大きなコレクションを static に保持しない。

4. その他のツール: Eclipse MAT、jconsole

Eclipse Memory Analyzer (MAT)

Eclipse MAT は無料かつ強力な heap dump 解析ツールです。どのオブジェクトがメモリを占有しているか、どれが他を保持しているか——いわゆる retained set——を可視化できます。これにより、リークの所在を正確に突き止められます。

疑わしいオブジェクト(leak suspects)に関する詳細レポートを生成でき、数ギガバイト級の巨大ダンプでも安定して扱えます。

使い方は簡単です:jmapjvisualvm でヒープダンプを取得し、MAT で開いて Leak Suspects Report を押すだけ。潜在的な問題がハイライトされたレポートが得られます。

jconsole

jconsole は、JVM をリアルタイム監視するためのシンプルで使いやすいツールです。使用メモリ量、CPU 負荷、稼働スレッド数、GC の挙動などを表示します。

VisualVM のような高機能ツールと違い、jconsoleheap dump を解析しませんが、素早い診断には最適です——アプリ内部で今何が起きているかをひと目で把握できます。

5. メモリ解析での典型的なミス

ミス 1: 別のプロセスのダンプを取ってしまう。 1 台で多くの Java アプリが動いていることはよくあり、PID を取り違えることがあります。jps で確認し、自分のプログラムを解析していることを必ずチェックしましょう。

ミス 2: ないところに「リーク」を期待してしまう。 GC は即座にオブジェクトを回収しないことがあります——メモリが「解放されたように見える」のは Full GC の後だけ、というケースも。1 回の GC でメモリが減らなくても慌てず、トレンドを見ましょう。

ミス 3: static/キャッシュされたオブジェクトの影響を見落とす。 多くのリークは、static フィールドやグローバルコレクションにオブジェクトを保持していることが原因です。「重い」オブジェクトを誰が参照しているかを確認しましょう——たいていは static です。

ミス 4: 時系列でのプロファイリングを使わない。 heap dump は有用ですが、ときにはメモリの時間的な増加(jvisualvm のグラフ)を見ることが重要です。メモリが安定して増え続けるなら、調査すべきサインです。

ミス 5: リスナー/イベントハンドラを解除しない。 イベントに購読したまま解除しないと、オブジェクトは使っていなくてもメモリに残り続けます。

アドバイス: ツールを恐れず試してみましょう! ダンプを取得し、グラフを眺め、オブジェクトをクリックする——そうしてはじめて、あなたのプログラムのメモリ感覚が身につき、問題を素早く見つけられるようになります。

コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION