概要
「このバッチ、メモリ足りてるのか」「List に何万件入れたらどのくらい消費するのか」――業務でこうした疑問を持ったとき、まず手軽に確認できるのが Runtime API によるヒープメモリの計測です。外部ツールやプロファイラを持ち出す前に、コード数行で概算を把握できるため、開発中の見積もりや障害時の一次切り分けに重宝します。この記事では、Runtime.getRuntime() から取得できる maxMemory / totalMemory / freeMemory の意味の違い、大量データ生成前後のメモリ変化の計測、GC 実行後の回収量の確認方法を扱います。record を使ったスナップショットの管理パターンも示すので、計測ロジックを使い回しやすくなります。
使いどころ
CSV 取込バッチで 10 万行を List に溜め込んだときのヒープ消費量を概算する
メモリ不足が疑われるとき、処理の前後で使用量を記録して増分を確認する
GC 後にどの程度メモリが回収されたかを確認し、リークの有無をざっくり判断する
コード例
import java.util.ArrayList;
public class MemoryMeasure {
// メモリスナップショットを record で管理(Java 17+)
record MemorySnapshot(long maxKb, long totalKb, long usedKb, long freeKb) {
/** 現在のヒープ状態をキャプチャ */
static MemorySnapshot capture() {
var rt = Runtime.getRuntime();
var total = rt.totalMemory();
var free = rt.freeMemory();
return new MemorySnapshot(
rt.maxMemory() / 1024,
total / 1024,
(total - free) / 1024,
free / 1024);
}
void print(String label) {
System.out.println("--- " + label + " ---");
System.out.printf("最大ヒープ : %,d KB%n", maxKb);
System.out.printf("確保済み : %,d KB%n", totalKb);
System.out.printf("使用中 : %,d KB%n", usedKb);
System.out.printf("空き : %,d KB%n", freeKb);
}
}
/** 大量データ生成とメモリ変化の計測 */
static void measureLargeList() {
MemorySnapshot.capture().print("リスト生成前");
var list = new ArrayList<String>();
for (int i = 0; i < 100_000; i++) {
list.add("item-" + i);
}
MemorySnapshot.capture().print("リスト生成後(10万件)");
// 参照を切って GC を要求
list = null;
System.gc();
try { Thread.sleep(100); }
catch (InterruptedException e) { Thread.currentThread().interrupt(); }
MemorySnapshot.capture().print("GC 後");
}
public static void main(String[] args) {
System.out.println("=== ヒープメモリ計測 ===");
measureLargeList();
}
}Version Coverage
record でスナップショットを不変オブジェクトとして管理できる。ファクトリメソッド(static メソッド)を record 内に定義すると、取得と保持を一箇所にまとめられる。
// Java 17: record + ファクトリメソッドでスナップショット管理
record MemorySnapshot(long maxKb, long totalKb, long usedKb, long freeKb) {
static MemorySnapshot capture() {
var rt = Runtime.getRuntime();
return new MemorySnapshot(
rt.maxMemory() / 1024, rt.totalMemory() / 1024,
(rt.totalMemory() - rt.freeMemory()) / 1024,
rt.freeMemory() / 1024);
}
}Library Comparison
注意点
Runtime.totalMemory() は「JVM が現在確保しているヒープ」であり、maxMemory() とは異なる。ヒープはアプリケーションの需要に応じて totalMemory が maxMemory まで拡張される
System.gc() は GC の実行を保証しない。計測コードでは便宜的に使うが、本番コードには入れないこと
メモリ計測は GC のタイミングに左右される。同じコードでも実行ごとに数値が変わるため、傾向を見ることが重要
Runtime API で取得できるのはヒープメモリのみ。DirectByteBuffer やネイティブメモリの消費は含まれない。NIO を使う場合は注意
FAQ
maxMemory は JVM が使える上限(-Xmx 相当)、totalMemory は現在確保されているヒープサイズです。アプリの需要に応じて totalMemory は maxMemory まで拡張されます。
GC のタイミングや JIT コンパイルの状況で使用量が変動します。1回の計測ではなく、複数回の傾向を見ることが重要です。
小規模データで計測した1件あたりのメモリ消費量を、想定件数に掛けて概算します。Runtime API での before / after 計測がそのまま見積もりの根拠になります。