Java Tips | 基本ユーティリティ:メモリ使用量取得

Java Java
スポンサーリンク

メモリ使用量取得は「今どれくらい余裕があるか」を知るための技

業務システムが重くなったり、OutOfMemoryError で落ちたりするとき、
「その瞬間にどれくらいメモリを使っていたのか」が分かるかどうかで、原因調査の難易度が大きく変わります。

Java には GC があるので、普段はメモリを意識せずに書けます。
でも、バッチ処理や大きなデータを扱う処理では、「今どれくらい使っていて、どれくらい空きがあるか」を把握しておくことが、実務ではかなり重要です。

そこで役に立つのが「メモリ使用量取得ユーティリティ」です。
Runtime や Management API を使って、現在のヒープ使用量を簡単に取れるようにしておくと、ログや監視にすぐ組み込めるようになります。


基本:Runtime からヒープの使用量を取得する

Runtime.getRuntime() で取れる三つの値

まずは一番基本的な API から押さえます。

public class MemoryBasic {

    public static void main(String[] args) {
        Runtime rt = Runtime.getRuntime();

        long total  = rt.totalMemory();   // 現在 JVM に割り当てられているヒープサイズ
        long free   = rt.freeMemory();    // そのうち「まだ使われていない」領域
        long max    = rt.maxMemory();     // JVM が使ってよい最大ヒープサイズ
        long used   = total - free;       // 実際に使用中のメモリ

        System.out.println("total = " + total);
        System.out.println("free  = " + free);
        System.out.println("used  = " + used);
        System.out.println("max   = " + max);
    }
}
Java

ここで重要なのは、totalMemorymaxMemory の違いです。

totalMemory は「今この瞬間、JVM が OS から確保しているヒープのサイズ」です。
maxMemory は「JVM が将来的に確保してよい上限(-Xmx で指定した値)」です。

つまり、「今の使用量」を見るときは used = total - free を使い、
「まだどれくらい増やせる余地があるか」を見るときは max - used を見る、というイメージになります。


実務で使えるメモリ使用量ユーティリティの最小形

バイトではなく MB 単位で扱いやすくする

Runtime が返す値はバイト単位なので、そのままだと桁が大きくて読みにくいです。
そこで、MB 単位に変換して返すユーティリティを作ってみます。

public final class MemoryUsage {

    private static final long MB = 1024L * 1024L;

    private MemoryUsage() {}

    public static long usedBytes() {
        Runtime rt = Runtime.getRuntime();
        return rt.totalMemory() - rt.freeMemory();
    }

    public static long maxBytes() {
        return Runtime.getRuntime().maxMemory();
    }

    public static long usedMB() {
        return usedBytes() / MB;
    }

    public static long maxMB() {
        return maxBytes() / MB;
    }

    public static String summary() {
        long used = usedMB();
        long max  = maxMB();
        return "memory used=" + used + "MB / max=" + max + "MB";
    }
}
Java

使う側はこう書けます。

System.out.println(MemoryUsage.summary());
Java

出力例は次のようになります。

memory used=128MB / max=512MB

ここで深掘りしたいポイントは二つです。

一つ目は、「バイトをそのまま扱わず、MB に変換して“人間が読める形”にしている」ことです。
ログや監視に載せるとき、134217728 より 128MB のほうが圧倒的に直感的です。

二つ目は、「使用量と上限をセットで出している」ことです。
「128MB 使っている」とだけ言われても、それが多いのか少ないのか分かりません。
「512MB 中 128MB」なら、「まだ余裕があるな」「そろそろ危ないな」といった判断がしやすくなります。


GC 前後でメモリ使用量を測るユーティリティ

「本当に足りないのか」を見るためのテクニック

一時的にオブジェクトがたくさん作られても、GC が走れば一気に解放されることがあります。
「GC したあとでも使用量が高止まりしているか」を見ることで、「本当にメモリが足りていないのか」「単に一時的に膨らんだだけか」を判断しやすくなります。

簡易的に「GC 前後の使用量」を測るユーティリティを書いてみます。

public final class MemoryInspector {

    private MemoryInspector() {}

    public static String gcAndSummary() {
        System.gc(); // ヒントを出すだけで、必ず GC が走るわけではない点には注意
        try {
            Thread.sleep(200); // 少し待って GC が終わる時間を与える
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        return MemoryUsage.summary();
    }
}
Java

使い方はこうです。

System.out.println("before: " + MemoryUsage.summary());
// 重い処理
System.out.println("after : " + MemoryInspector.gcAndSummary());
Java

ここでの重要ポイントは、「System.gc() は“お願い”であって、必ず GC が走る保証はない」ということです。
ただ、開発環境や検証環境で「ざっくり傾向を見る」には十分役に立ちます。

本番で頻繁に System.gc() を呼ぶのは基本的に推奨されませんが、
「調査用の一時的なコード」や「手動トリガーの診断エンドポイント」などでは、こうしたユーティリティが便利です。


ManagementFactory を使ったより詳細なメモリ情報

MemoryMXBean からヒープ使用量を取る

Runtime だけでも十分ですが、java.lang.management パッケージを使うと、もう少し構造化された情報が取れます。

import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
import java.lang.management.MemoryUsage;

public class MemoryMxSample {

    public static void main(String[] args) {
        MemoryMXBean bean = ManagementFactory.getMemoryMXBean();
        MemoryUsage heap = bean.getHeapMemoryUsage();

        long used = heap.getUsed();
        long committed = heap.getCommitted();
        long max = heap.getMax();

        System.out.println("used     = " + used);
        System.out.println("committed= " + committed);
        System.out.println("max      = " + max);
    }
}
Java

ここで出てくる committed は、「OS から確保済みのヒープサイズ」で、Runtime.totalMemory() とほぼ同じイメージです。
used は実際に使っている量、max は上限です。

この API を使ったユーティリティも作れますが、初心者向けにはまず Runtime ベースで慣れてから、
必要に応じて Management API に広げていくのがよいです。


メモリ使用量をログや監視に組み込む

バッチ処理の節目でメモリを記録する

例えば、大量データを処理するバッチで、「1 万件ごとにメモリ使用量をログに出す」といった使い方ができます。

for (int i = 0; i < total; i++) {
    process(record(i));
    if (i % 10_000 == 0) {
        log.info("progress={} / {}, {}", i, total, MemoryUsage.summary());
    }
}
Java

ログには次のように出ます。

progress=10000 / 100000, memory used=256MB / max=512MB

これを見るだけで、「処理が進むにつれてメモリがじわじわ増えているか」「あるところで頭打ちになっているか」が分かります。
「進むほど増え続けている」ならリークの疑いがありますし、「ある程度で安定している」なら GC がうまく効いていると推測できます。

ヘルスチェックや診断エンドポイントに載せる

Web アプリなら、「/actuator/health」のようなヘルスチェックや、「/diagnostics」のような診断エンドポイントに、
MemoryUsage.summary() の結果を載せておくのも有効です。

運用担当者がブラウザや監視ツールから「今このインスタンスはどれくらいメモリを使っているか」をすぐ確認できるようになります。


メモリ使用量取得で気をつけるべきこと

「数字だけを見て焦らない」

Java のヒープは、空きがあってもすぐには OS に返されません。
また、GC のタイミングによって、一時的に使用量が増えたり減ったりします。

そのため、「一瞬だけ used が max に近づいた」ことだけを見て「すぐに OutOfMemory だ」と決めつけるのは危険です。
大事なのは、「時間軸でどう変化しているか」「GC の後でも高止まりしているか」といった“傾向”を見ることです。

メモリ使用量だけでなく、ヒープサイズの設定も見る

maxMemory が 256MB に設定されている環境で「200MB 使っている」のと、
maxMemory が 4GB の環境で「200MB 使っている」のでは、意味がまったく違います。

メモリ使用量ユーティリティを使うときは、「ヒープ上限(-Xmx)がいくつに設定されているか」もセットで意識する癖をつけておくと、数字の解釈を間違えにくくなります。


まとめ:メモリ使用量取得ユーティリティで身につけるべき感覚

メモリ使用量取得は、「ただ数字を取る」だけではなく、「その数字をどう解釈し、どこに記録し、どう設計に活かすか」という話です。

押さえておきたい感覚は次の通りです。

Runtime から totalMemory, freeMemory, maxMemory を取り、「used = total – free」で使用量を出す。
バイトのままではなく、MB など人間が読みやすい単位に変換してログや監視に載せる。
使用量と上限をセットで出し、「どれくらい余裕があるか」を一目で分かるようにする。
必要に応じて GC 前後の使用量を測り、「本当に足りないのか」「一時的な増加なのか」を見分ける。
バッチ処理の節目やヘルスチェックに組み込んで、「時間軸での変化」を追えるようにする。

タイトルとURLをコピーしました