java-recipes

ホーム パフォーマンス計測 › Perf-02

Perf-02: メモリ使用量測定

Java でヒープメモリの使用量を計測する方法を解説します。Runtime.getRuntime() を使った 最大ヒープ・確保済みヒープ・使用中メモリの取得方法、GC 前後での変化の観察、OutOfMemoryError 対策の基礎を学びましょう。

なぜメモリ計測が必要か

  • 大量データを処理するバッチで OutOfMemoryError が発生したとき、原因を特定するため
  • データ量に応じてメモリ使用量がどのくらい増えるか事前に見積もりたいとき
  • GC が頻繁に発生して処理が遅い場合、メモリ使用パターンを確認するため
  • 本番環境に近い条件でリークが発生していないかテストするとき

Runtime で取得できるメモリ情報

メソッド意味補足
maxMemory()JVM が使える最大ヒープサイズ-Xmx で設定した値
totalMemory()JVM が現在確保しているヒープサイズ最初は maxMemory より小さい場合がある
freeMemory()確保済みヒープの空き容量usedMemory = totalMemory - freeMemory

サンプルコード

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

public class MemoryUsageSample {

    // ヒープメモリ情報を表示するヘルパー
    static void printMemoryInfo(String label) {
        Runtime runtime = Runtime.getRuntime();
        long maxMemory = runtime.maxMemory();
        long totalMemory = runtime.totalMemory();
        long freeMemory = runtime.freeMemory();
        long usedMemory = totalMemory - freeMemory;

        System.out.println("--- " + label + " ---");
        System.out.printf("最大ヒープ   : %,d KB%n", maxMemory / 1024);
        System.out.printf("確保済みヒープ: %,d KB%n", totalMemory / 1024);
        System.out.printf("使用中       : %,d KB%n", usedMemory / 1024);
        System.out.printf("空き         : %,d KB%n", freeMemory / 1024);
    }

    // 大量データを生成してメモリ変化を計測
    static void measureLargeList() {
        printMemoryInfo("リスト生成前");
        List<String> list = new ArrayList<>();
        for (int i = 0; i < 100_000; i++) {
            list.add("item-" + i);
        }
        printMemoryInfo("リスト生成後(100万件)");
        list = null;
        System.gc(); // GC 要求(実行は JVM 次第)
        try { Thread.sleep(100); } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
        printMemoryInfo("GC 後");
    }

    public static void main(String[] args) {
        System.out.println("=== ヒープメモリ計測 ===");
        measureLargeList();
    }
}

Java 8 では Runtime.getRuntime() でヒープメモリの情報を取得します。usedMemory = totalMemory - freeMemory の計算でその時点の使用中メモリが分かります。

よくあるミス・注意点

System.gc() は GC のヒントであり、強制実行ではない

System.gc() を呼んでも、 JVM が必ずしも即座に GC を実行するとは限りません。 JVM の実装や状況によっては無視されます。 GC 後の計測値を確認したい場合は、gc() の後に少し待機する(Thread.sleep)と実行されやすくなりますが、保証はありません。

JVM 起動直後は totalMemory が maxMemory より小さい

JVM は起動時に maxMemory 全てを確保するのではなく、必要に応じてヒープを拡張します。 そのため起動直後は totalMemory が maxMemory より小さい状態です。 「空き容量 = maxMemory - usedMemory」と誤って計算すると、実際より多くの空きがあるように見えてしまいます。 正しくは「usedMemory = totalMemory - freeMemory」で計算します。

参照を null にしただけでは即座にメモリが解放されない

変数に null を代入してもオブジェクトはすぐに解放されません。 GC が実行されたときに初めてメモリが回収されます。 大量データを処理した後、freeMemory の値がすぐに増えないのはこのためです。

テストする観点

  • 大量データ生成後に usedMemory がリスト生成前より増加していること
  • GC 前後で freeMemory の値が変化すること(増加が期待されるが JVM 依存)
  • maxMemory が 0 より大きい値を返すこと(JVM の設定に依存)
  • usedMemory = totalMemory - freeMemory の計算が常に 0 以上であること
  • データ量(要素数)を増やすほどピーク時の usedMemory が増加する傾向があること

GitHub でソースコードを見る →