メモリ使用量取得は「今どれくらい余裕があるか」を知るための技
業務システムが重くなったり、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ここで重要なのは、totalMemory と maxMemory の違いです。
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 前後の使用量を測り、「本当に足りないのか」「一時的な増加なのか」を見分ける。
バッチ処理の節目やヘルスチェックに組み込んで、「時間軸での変化」を追えるようにする。
