JVMオプションとは、java コマンドに渡してヒープサイズ・GCの種類・ログ・障害時の動作などを決める起動引数です。-Xmx(最大ヒープ)と -Xms(初期ヒープ)が代表で、ほかに GC の選択、GC ログ、メモリ不足時のヒープダンプなどを指定します。

この記事では、用途別のオプション一覧表、Java 8 の書き方から Java 11 以降への書き換え表、既定値の実測(PrintFlagsFinal)、小さいヒープで OutOfMemoryError を起こして GC ログとヒープダンプを取る手順を、OpenJDK 21.0.10 で実行した結果つきでまとめました。Java 25 での違いは公式ドキュメントで確認して注記しています。

実行環境:OpenJDK 21.0.10(Ubuntu 24.04)、2 CPU、コンテナのメモリ上限 5.84GB(cgroup v1)。既定値は CPU 数とメモリ量で変わるため、数値は「この環境での例」として読んでください。

JVMオプションの早見表(用途別)

用途 オプション 例 補足
最大ヒープ -Xmx -Xmx2g 既定は物理メモリ(コンテナなら上限)の 1/4
初期ヒープ -Xms -Xms2g サーバーでは -Xmx と同じ値にすることが多い
ヒープを割合で指定 -XX:MaxRAMPercentage -XX:MaxRAMPercentage=75 コンテナ向け。-Xmx があればそちらが優先
初期ヒープを割合で -XX:InitialRAMPercentage -XX:InitialRAMPercentage=50 既定 1.5625%
スレッドのスタック -Xss -Xss512k 既定 1MB(Linux x64)
メタスペース上限 -XX:MaxMetaspaceSize -XX:MaxMetaspaceSize=256m 既定は無制限
ダイレクトメモリ上限 -XX:MaxDirectMemorySize -XX:MaxDirectMemorySize=512m 既定 0(=最大ヒープと同じ扱い)
GC:G1(既定) -XX:+UseG1GC 2 CPU 以上・メモリ 1792MB 以上で自動選択
GC:ZGC -XX:+UseZGC Java 21 は -XX:+ZGenerational も付ける 停止時間を短くしたい大きなヒープ向け
GC:Parallel -XX:+UseParallelGC スループット重視のバッチ
GC:Serial -XX:+UseSerialGC 1 CPU・小さなヒープ
GC 停止時間の目標 -XX:MaxGCPauseMillis =200(G1 の既定) 目標値で保証ではない
GC ログ -Xlog:gc* -Xlog:gc*:file=gc.log:time,uptime,level,tags:filecount=5,filesize=10m Java 9 以降の書き方
OOM 時にヒープダンプ -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/dump/ 出力先は書き込み可能な場所に
OOM 時にすぐ終了 -XX:+ExitOnOutOfMemoryError 再起動を外部(コンテナ基盤など)に任せる
JFR(記録) -XX:StartFlightRecording =duration=60s,filename=rec.jfr 本番でも低負荷で記録できる
リモートデバッグ -agentlib:jdwp =transport=dt_socket,server=y,suspend=n,address=127.0.0.1:5005 公開範囲に注意
設定値の確認 -XX:+PrintFlagsFinal java -XX:+PrintFlagsFinal -version 全フラグの最終値
実行中の確認 jcmd <pid> VM.flags 起動済みプロセスの値
環境変数で渡す JAVA_TOOL_OPTIONS/JDK_JAVA_OPTIONS 違いは後述

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「JVMオプション一覧|Xmx・Xms・GCログ設定をJava 21対応で」

オプションの3つの種類と読み方

JVMオプションは、書き方で「標準」「-X(非標準)」「-XX(詳細)」の3種類に分かれます。

種類 書き方 例 特徴
標準オプション - で始まる -cp、-jar、-D名前=値、-version どの JVM でも使える
非標準(-X) -X で始まる -Xmx、-Xms、-Xss、-Xlog HotSpot 系で共通。java -X で一覧
詳細(-XX) -XX: で始まる -XX:+UseG1GC、-XX:MaxRAMPercentage=75 細かな調整。バージョンで増減がある

-XX の真偽値は + で有効、- で無効(-XX:+UseZGC、-XX:-UseContainerSupport)、数値は = で指定します。サイズは k・m・g の単位が使えます(-Xmx512m、-Xmx2g)。

既定値を実測する(PrintFlagsFinal と jcmd)

java -XX:+PrintFlagsFinal -version

-XX:+PrintFlagsFinal は、そのマシンで JVM が実際に使う全フラグの最終値を表示します。オプションなしで実行し、主要な行を抜き出した結果です。

java -XX:+PrintFlagsFinal -version | grep -E ' (MaxHeapSize|InitialHeapSize|MaxRAMPercentage|UseG1GC|ThreadStackSize|MaxMetaspaceSize|UseContainerSupport) '
   size_t InitialHeapSize                          = 98566144                                  {product} {ergonomic}
   size_t MaxHeapSize                              = 1568669696                                {product} {ergonomic}
   size_t MaxMetaspaceSize                         = 18446744073709551615                      {product} {default}
   double MaxRAMPercentage                         = 25.000000                                 {product} {default}
     intx ThreadStackSize                          = 1024                                   {pd product} {default}
     bool UseContainerSupport                      = true                                      {product} {default}
     bool UseG1GC                                  = true                                      {product} {ergonomic}
項目 値 読み方
MaxHeapSize 1568669696(約1496MB) コンテナ上限 5.84GB × 25%
InitialHeapSize 98566144(約94MB) 上限 × 1.5625%
MaxMetaspaceSize 18446744073709551615 実質無制限
ThreadStackSize 1024 KB 単位で 1MB
末尾の {ergonomic} JVM が環境から自動で決めた値
末尾の {default}/{command line} 既定値/コマンドラインで指定した値

{ergonomic} の値は、CPU 数やメモリ量で変わります。開発機と本番機でヒープが違う原因はここにあるので、本番と同じ環境で確認するのが確実です。人が読みやすい形なら java -XshowSettings:vm -version で Max. Heap Size (Estimated): 1.46G、-XshowSettings:system で Memory Limit: 5.84G のようにコンテナの上限も表示できました。

オプションを変えたときのヒープと GC(実測)

同じマシンで、オプションだけを変えて MaxHeapSize などを比べた結果です。

指定したオプション InitialHeapSize MaxHeapSize 選ばれた GC
なし 94MB 1496MB(上限の25%) G1
-XX:-UseContainerSupport 126MB 2008MB(物理 8GB の25%) G1
-XX:MaxRAMPercentage=75 96MB 4488MB G1
-XX:InitialRAMPercentage=50 -XX:MaxRAMPercentage=75 2992MB 4488MB G1
-Xms512m -Xmx512m 512MB 512MB G1
-Xmx512m -XX:MaxRAMPercentage=75 94MB 512MB(-Xmx が優先) G1
-XX:MaxRAM=1g 16MB 256MB G1
-XX:MaxRAM=200m 8MB 100MB(小メモリでは50%) G1
-XX:ActiveProcessorCount=1 94MB 1496MB Serial

ここからわかることは4つです。

  1. コンテナでは、物理メモリではなくコンテナの上限を基準にヒープが決まります(UseContainerSupport は既定で true)。
  2. -Xmx と MaxRAMPercentage を両方書くと -Xmx が優先されます。
  3. メモリが小さい環境では MinRAMPercentage(既定50%)が使われ、割合が大きくなります。
  4. CPU が1つと判断されると、GC は G1 ではなく Serial が自動で選ばれます。コンテナに CPU を1つだけ割り当てると GC が変わるのはこのためです。

実行中のプロセスを jcmd で確認する

起動済みの JVM の設定は jcmd で確認できます。-Xms256m -Xmx256m -XX:+UseZGC と JFR を付けて起動したプロセスに対する実行結果です。

jcmd -l                     # 起動中の JVM 一覧
jcmd <pid> VM.flags         # 実際に効いているフラグ
jcmd <pid> VM.command_line  # 起動時の引数
jcmd <pid> GC.heap_info     # ヒープの使用状況
$ jcmd -l
13236 Server
$ jcmd 13236 VM.flags
13236:
-XX:CICompilerCount=2 -XX:+FlightRecorder -XX:InitialHeapSize=268435456 -XX:MaxHeapSize=268435456 -XX:MinHeapDeltaBytes=2097152 -XX:MinHeapSize=268435456 ... -XX:-UseCompressedOops -XX:+UseFastUnorderedTimeStamps -XX:+UseZGC -XX:-ZUncommit
$ jcmd 13236 VM.command_line
VM Arguments:
jvm_args: -Xms256m -Xmx256m -XX:+UseZGC -XX:StartFlightRecording=duration=30s,filename=rec.jfr
java_command: Server
$ jcmd 13236 GC.heap_info
 ZHeap           used 40M, capacity 256M, max capacity 256M
 Metaspace       used 4007K, committed 4224K, reserved 1114112K

VM.flags には JVM が自動で追加した値も含まれます。ZGC では -XX:-UseCompressedOops が自動で設定されているのがわかります。jcmd <pid> VM.flags -all なら PrintFlagsFinal と同じ形式で全フラグが見られ、この環境では ZGenerational = false {default} と表示されました(Java 21 の ZGC は既定で非世代別。後述)。

ヒープ・メモリのオプション

オプション 既定値(この環境) 用途 目安
-Xmx 上限の25% 最大ヒープ 物理サーバーなら明示する
-Xms 上限の1.5625% 初期ヒープ -Xmx と同じにすると起動後の拡張がなくなる
-XX:MaxRAMPercentage 25 最大ヒープを割合で コンテナは 50〜75 が目安
-XX:InitialRAMPercentage 1.5625 初期ヒープを割合で
-XX:MinRAMPercentage 50 小メモリ環境での最大ヒープ割合 名前に反して「最大」の計算に使う
-Xss 1MB 1スレッドのスタック 深い再帰がなければ既定でよい
-XX:MetaspaceSize 約21MB メタスペースの初回 GC 閾値
-XX:MaxMetaspaceSize 無制限 クラス情報の上限 上限を決めると漏れに早く気づける
-XX:MaxDirectMemorySize 0(最大ヒープ相当) NIO のダイレクトバッファ
-XX:ReservedCodeCacheSize 240MB JIT のコード領域

ヒープ以外のメモリ(メタスペース、スタック×スレッド数、コードキャッシュ、ダイレクトバッファ)もプロセスのメモリに含まれます。そのため、コンテナのメモリ上限いっぱいに -Xmx を設定すると、合計が上限を超えてコンテナが強制終了されることがあります。ヒープはコンテナ上限の 50〜75% にして、残りをヒープ以外に回すのが目安です。

-Xss とスタックの深さ(実測)

再帰を StackOverflowError まで続け、到達した深さを比べました。

public class Depth {
    static int depth = 0;
    static void recurse() { depth++; recurse(); }
    public static void main(String[] args) {
        try { recurse(); } catch (StackOverflowError e) { System.out.println("depth=" + depth); }
    }
}
-Xss256k: depth=3644
-Xss512k: depth=10198
-Xss1m: depth=22275
-Xss4m: depth=141446

スタックを小さくすると、1スレッドあたりのメモリは減りますが、深い再帰で StackOverflowError になりやすくなります。深さは JIT の状態でも変わるので目安です。スレッドを大量に作るアプリでは -Xss を小さくする選択肢がありますが、Java 21 なら仮想スレッドを使う方法もあります(「Java 仮想スレッド入門」記事を参照)。

GC の選び方とオプション

GC 指定 向いている用途 Java 21 の注意
G1 -XX:+UseG1GC 一般的なサーバーアプリ(既定) 2 CPU 以上かつ 1792MB 以上で自動選択
ZGC -XX:+UseZGC -XX:+ZGenerational 大きなヒープで停止時間を短く 21 は ZGenerational を付けないと非世代別
Parallel -XX:+UseParallelGC バッチなど総処理量重視
Serial -XX:+UseSerialGC 1 CPU・小さなヒープ・CLI ツール 1 CPU 環境では自動選択
Shenandoah -XX:+UseShenandoahGC 停止時間を短く 配布元のビルドによる(Ubuntu の OpenJDK 21 では利用可能だった)

各 GC を -Xlog:gc 付きで起動したときの1行目です。

[-XX:+UseSerialGC]    [0.004s][info][gc] Using Serial
[-XX:+UseParallelGC]  [0.004s][info][gc] Using Parallel
[-XX:+UseG1GC]        [0.004s][info][gc] Using G1
[-XX:+UseZGC]         [0.018s][info][gc] Using The Z Garbage Collector
[-XX:+UseShenandoahGC][0.007s][info][gc] Using Shenandoah

迷ったら既定の G1 のままにし、GC ログで停止時間を確認してから変えるのが確実です。G1 の主な調整は -XX:MaxGCPauseMillis(停止時間の目標、既定200ms)くらいで、多くの場合はヒープサイズを適切にするだけで十分です。

GC ログの設定(-Xlog:gc*)

Java 9 以降の書き方

Java 9 以降、GC ログは統合ログ(Unified Logging)の -Xlog で指定します。形式は -Xlog:対象:出力先:装飾:出力オプション です。

java -Xlog:gc*:file=/var/log/app/gc-%p-%t.log:time,uptime,level,tags:filecount=5,filesize=10m -jar app.jar
部分 値 意味
対象 gc* gc で始まるタグすべて(gc だけなら概要のみ)
出力先 file=gc-%p-%t.log %p は PID、%t は起動時刻に置き換わる
装飾 time,uptime,level,tags 日時・起動からの秒・レベル・タグを行頭に付ける
出力オプション filecount=5,filesize=10m 10MB ごとに5世代でローテーション

装飾には time、utctime、uptime、timemillis、uptimemillis、pid、tid、level、tags などがあり、java -Xlog:help で一覧を表示できます。

Java 8 からの書き換え表

Java 8 の GC ログ用オプションの多くは、Java 21 では使えません。実際に Java 21.0.10 に渡した結果をあわせて載せます。

Java 8 の書き方 Java 11 以降の書き方 Java 21 に渡した結果
-XX:+PrintGC -Xlog:gc 警告して -Xlog:gc として動く
-XX:+PrintGCDetails -Xlog:gc* 警告して -Xlog:gc* として動く
-Xloggc:gc.log -Xlog:gc:file=gc.log 警告して動く
-XX:+PrintGCDateStamps 装飾 time 起動しない
-XX:+PrintGCTimeStamps 装飾 uptime 起動しない
-XX:+UseGCLogFileRotation、NumberOfGCLogFiles、GCLogFileSize filecount=5,filesize=10m 起動しない
-XX:+PrintGCApplicationStoppedTime -Xlog:safepoint (未実行)
-XX:+PrintTenuringDistribution -Xlog:gc+age=trace (未実行)
-XX:+PrintHeapAtGC -Xlog:gc+heap=debug (未実行)
-XX:PermSize/-XX:MaxPermSize -XX:MetaspaceSize/-XX:MaxMetaspaceSize 起動しない
-XX:+UseConcMarkSweepGC、-XX:+UseParNewGC -XX:+UseG1GC(既定) 起動しない
-XX:+AggressiveOpts なし(削除) 起動しない

(未実行)の行は、Oracle の java コマンド公式ドキュメントの変換表に沿って記載しています。警告と起動エラーの実際の出力です。

$ java -XX:+PrintGCDetails -version
[0.001s][warning][gc] -XX:+PrintGCDetails is deprecated. Will use -Xlog:gc* instead.

$ java -Xloggc:old.log -version
[0.000s][warning][gc] -Xloggc is deprecated. Will use -Xlog:gc:old.log instead.

$ java -XX:+PrintGCDateStamps -version
Unrecognized VM option 'PrintGCDateStamps'
Error: Could not create the Java Virtual Machine.
Error: A fatal exception has occurred. Program will exit.

$ java -XX:MaxPermSize=256m -version
Unrecognized VM option 'MaxPermSize=256m'
Error: Could not create the Java Virtual Machine.

Java 8 時代の起動スクリプトをそのまま使うと、JVM が起動しないことがあります。移行時は java <オプション> -version で1つずつ確かめ、上の表で書き換えます。-XX:+IgnoreUnrecognizedVMOptions で未知のオプションを無視させることもできますが、設定が効いていないことに気づきにくくなるため、書き換える方が確実です。

OutOfMemoryError を起こして GC ログとヒープダンプを取る

ここでは、わざと小さいヒープ(64MB)でメモリ不足を起こし、GC ログとヒープダンプが取れることを確かめます。本番で同じ設定をしておけば、問題が起きたときに原因を調べる材料が残ります。メッセージ別の原因と対処は「Java OutOfMemoryErrorの原因と対処」記事で詳しく扱っているので、ここでは取得手順に絞ります。

1. 再現用のコード

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

public class OomDemo {
    // 注文データのキャッシュ(削除しないので増え続ける)
    static final List<byte[]> cache = new ArrayList<>();

    public static void main(String[] args) throws Exception {
        Runtime rt = Runtime.getRuntime();
        System.out.printf("maxMemory=%dMB%n", rt.maxMemory() / 1024 / 1024);
        for (int i = 1; ; i++) {
            cache.add(new byte[100 * 1024]); // 100KB ずつ追加
            if (i % 100 == 0) {
                System.out.printf("added=%dMB used=%dMB%n", i / 10,
                        (rt.totalMemory() - rt.freeMemory()) / 1024 / 1024);
            }
            Thread.sleep(1);
        }
    }
}

2. GC ログとヒープダンプを有効にして実行

javac OomDemo.java
mkdir -p dumps
java -Xms64m -Xmx64m \
  -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=dumps/ \
  -Xlog:gc*:file=gc.log:time,uptime,level,tags:filecount=5,filesize=10m \
  -cp . OomDemo
maxMemory=64MB
added=10MB used=11MB
added=20MB used=21MB
added=30MB used=31MB
added=40MB used=41MB
added=50MB used=51MB
added=60MB used=61MB
java.lang.OutOfMemoryError: Java heap space
Dumping heap to dumps/java_pid12236.hprof ...
Heap dump file created [67147133 bytes in 0.032 secs]

Exception: java.lang.OutOfMemoryError thrown from the UncaughtExceptionHandler in thread "main"
$ ls -l dumps gc.log
-rw-r--r-- 1 root root 61193 Oct  2 05:00 gc.log
-rw------- 1 root root 67147133 Oct  2 05:00 java_pid12236.hprof

ヒープダンプは約64MBで、最大ヒープとほぼ同じ大きさになりました。ヒープダンプはヒープの中身をそのまま書き出すため、-Xmx8g なら最大で数GBのファイルになります。HeapDumpPath にはヒープサイズ以上の空きがある場所を指定してください。ファイル名は java_pid<PID>.hprof で、ディレクトリを指定するとその中に作られます。

3. GC ログを読む

gc.log から [gc ] タグの行(各 GC の要約)を抜き出した結果です。

[2026-10-02T05:00:38.767+0900][0.005s][info][gc     ] Using G1
[2026-10-02T05:00:38.904+0900][0.143s][info][gc          ] GC(0) Pause Young (Normal) (G1 Evacuation Pause) 7M->6M(64M) 5.372ms
[2026-10-02T05:00:38.996+0900][0.234s][info][gc          ] GC(2) Pause Young (Normal) (G1 Evacuation Pause) 13M->13M(64M) 3.466ms
[2026-10-02T05:00:39.250+0900][0.488s][info][gc          ] GC(4) Pause Young (Normal) (G1 Evacuation Pause) 32M->32M(64M) 11.685ms
[2026-10-02T05:00:39.354+0900][0.592s][info][gc          ] GC(6) Concurrent Mark Cycle
[2026-10-02T05:00:39.431+0900][0.670s][info][gc          ] GC(7) Pause Young (Prepare Mixed) (G1 Evacuation Pause) 46M->46M(64M) 6.532ms
....
[2026-10-02T05:00:39.737+0900][0.976s][info][gc             ] GC(31) Pause Full (G1 Compaction Pause) 61M->61M(64M) 4.962ms
[2026-10-02T05:00:39.743+0900][0.981s][info][gc             ] GC(34) Pause Full (G1 Compaction Pause) 61M->61M(64M) 4.436ms
[2026-10-02T05:00:39.748+0900][0.986s][info][gc             ] GC(35) Pause Full (G1 Compaction Pause) 61M->61M(64M) 4.703ms
部分 例 意味
GC(4) 起動から何回目の GC か
Pause Young (Normal) 新しいオブジェクトの領域を回収(短い停止)
Pause Full ヒープ全体の回収(長い停止になりやすい)
32M->32M(64M) 前→後(全体) GC 後も減っていない=回収できるものがない
11.685ms その GC での停止時間

読むポイントは「GC 後の使用量が下がらないまま増え続けるか」と「Pause Full が連続していないか」です。このログでは -> の後ろが 6M→13M→32M→46M→61M と増え続け、最後は Full GC が 20 回出ても 61M のまま減らず、OutOfMemoryError になりました。これはメモリリーク(またはヒープ不足)の典型的な形です。

4. 実行中のプロセスからヒープダンプを取る(jcmd/jmap)

OOM を待たずに、動いているプロセスからダンプを取ることもできます。

jcmd <pid> GC.heap_dump /tmp/dumps/server.hprof
jmap -dump:live,format=b,file=/tmp/dumps/server-jmap.hprof <pid>
jcmd <pid> GC.class_histogram | head   # ダンプを取らずにクラス別の件数とサイズを見る
$ jcmd 13236 GC.heap_dump .../dumps/server.hprof
Dumping heap to .../dumps/server.hprof ...
Heap dump file created [6784640 bytes in 0.055 secs]

$ jcmd 13236 GC.class_histogram | head -6
 num     #instances         #bytes  class name (module)
-------------------------------------------------------
   1:         10945         754040  [B (java.base@21.0.10)
   2:            50         579360  [Ljava.util.concurrent.ConcurrentHashMap$Node; (java.base@21.0.10)
   3:          1699         336272  java.lang.Class (java.base@21.0.10)
   4:         10011         320352  java.lang.String (java.base@21.0.10)
方法 使いどころ 注意
-XX:+HeapDumpOnOutOfMemoryError OOM の瞬間を自動で残す 本番では常に付けておくと安心
jcmd <pid> GC.heap_dump 動作中の任意のタイミング 取得中はアプリが一時停止する
jmap -dump:live,... 同上(従来からの方法) live で到達可能なオブジェクトだけ(事前に Full GC)
jcmd <pid> GC.class_histogram まず何が多いかをざっと見る ファイルを作らず軽い

取得した .hprof は Eclipse Memory Analyzer(MAT)や VisualVM で開き、どのオブジェクトがメモリを保持しているかを調べます。ヒープダンプには処理中のデータがそのまま含まれるため、保管場所と共有範囲には気を付けて扱いましょう。

OOM 時にすぐ終了させる(ExitOnOutOfMemoryError)

OOM が起きた JVM は、一部のスレッドだけが止まった中途半端な状態で動き続けることがあります。コンテナやサービス管理の仕組みで自動再起動できる環境なら、すぐに終了させる方が復旧が速くなります。

java -Xmx64m -XX:+ExitOnOutOfMemoryError -cp . OomDemo
added=60MB used=61MB
Terminating due to java.lang.OutOfMemoryError: Java heap space
$ echo $?
3

終了コード 3 で即座に終了しました。-XX:+HeapDumpOnOutOfMemoryError と組み合わせれば「ダンプを残してから終了」になります。終了ではなく JVM のクラッシュログ(hs_err ファイル)を残したい場合は -XX:+CrashOnOutOfMemoryError もあります。

JFR(Java Flight Recorder)で記録する

JFR は JVM に組み込まれた低負荷の記録機能で、GC・スレッド・CPU・メモリ割り当てなどのイベントをファイルに残せます。

java -XX:StartFlightRecording=duration=30s,filename=rec.jfr -jar app.jar
jcmd <pid> JFR.check                               # 記録中か確認
jcmd <pid> JFR.dump filename=now.jfr               # 途中経過を保存
jfr summary rec.jfr                                # 中身の概要
jfr print --events jdk.GCHeapSummary rec.jfr       # 特定イベントを表示
[1.578s][info][jfr,startup] Started recording 1. The result will be written to:
...
$ jcmd 13236 JFR.check
Recording 1: name=1 duration=30s (running)
$ ls -l rec.jfr
-rw-r--r-- 1 root root 237851 Oct  2 05:01 rec.jfr
$ jfr summary rec.jfr
 Version: 2.1
 Chunks: 1
 Start: 2026-10-01 20:01:18 (UTC)
 Duration: 30 s

30秒の記録で約232KBでした。記録ファイルは JDK Mission Control(JMC)で開くとグラフで確認できます。

デバッグ用オプション

java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=127.0.0.1:7340 -cp . HeapInfo
Listening for transport dt_socket at address: 7340

address は 127.0.0.1:ポート のようにローカルに限定するのが基本です。*:ポート にすると外部から接続できる状態になります(なお今回の環境では *:7340 は IPv6 のソケットが作れず transport error 202 で起動しませんでした)。suspend=y にするとデバッガが接続するまで起動を待ちます。

コンテナで動かすときの設定

Java 10 以降(8u191 以降も)は、コンテナの CPU とメモリの上限を自動で認識します(-XX:+UseContainerSupport が既定で有効。Java 25 の公式ドキュメントでも同じ説明です)。実測のとおり、この環境ではコンテナ上限 5.84GB を基準に最大ヒープが決まりました。

設定 おすすめ 理由
最大ヒープ -XX:MaxRAMPercentage=75(小さいコンテナは 50〜60) 上限を変えても自動で追従する
初期ヒープ -XX:InitialRAMPercentage=50 など 起動直後の拡張を減らす
-Xmx の固定値 使わない(使うなら上限より十分小さく) 上限変更のたびに書き換えが必要
CPU 数 2 以上を割り当てる、または GC を明示 1 CPU だと Serial GC が自動選択される
OOM 時 -XX:+ExitOnOutOfMemoryError+ヒープダンプ 基盤の自動再起動に任せる
ダンプ出力先 永続ボリューム コンテナ削除で消えないように
# Dockerfile の例(イメージ名は利用している JDK/JRE イメージに合わせる)
ENV JAVA_TOOL_OPTIONS="-XX:MaxRAMPercentage=75 -XX:+ExitOnOutOfMemoryError -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/dumps"

コンテナ実行(docker run -m 512m など)での確認は今回の環境では行っていません。上の実測はホスト自体のコンテナ上限(cgroup)と -XX:MaxRAM による疑似的な制限で確認したものです。

JAVA_TOOL_OPTIONS と JDK_JAVA_OPTIONS の違い

どちらも環境変数で JVM オプションを渡す方法ですが、読み取る対象と優先順位が違います。Java 21.0.10 で確かめた結果です。

試したこと 結果
JAVA_TOOL_OPTIONS="-Xmx256m" java HeapInfo Picked up JAVA_TOOL_OPTIONS: -Xmx256m → maxMemory=256MB
同上+コマンドラインで -Xmx128m 128MB(コマンドラインが優先)
JDK_JAVA_OPTIONS="-Xmx300m" java HeapInfo NOTE: Picked up JDK_JAVA_OPTIONS: -Xmx300m → 300MB
同上+コマンドラインで -Xmx128m 128MB(コマンドラインが優先)
両方を設定(256m と 300m) 300MB(JDK_JAVA_OPTIONS が優先)
JDK_JAVA_OPTIONS="-jar app.jar" Error: Option -jar is not allowed in environment variable JDK_JAVA_OPTIONS
javac 実行時 JAVA_TOOL_OPTIONS は読まれる(Picked up ... が出る)、JDK_JAVA_OPTIONS は読まれない
項目 JAVA_TOOL_OPTIONS JDK_JAVA_OPTIONS
読み取るもの JVM 本体(java、javac、jcmd など JVM で動くツールすべて) java ランチャーだけ(Java 9 以降)
優先順位 低い(JDK_JAVA_OPTIONS、コマンドラインに上書きされる) JAVA_TOOL_OPTIONS より高く、コマンドラインより低い
書けるもの JVM オプション ランチャーのオプションも可(-jar などメインクラス指定は不可)
起動時の表示 Picked up JAVA_TOOL_OPTIONS: ... NOTE: Picked up JDK_JAVA_OPTIONS: ...
向いている場面 Java 8 も含め確実に効かせたい、エージェントの追加 java コマンドだけに効かせたい

javac やビルドツールにまで効いてほしくない設定(大きなヒープなど)は JDK_JAVA_OPTIONS、どの JVM でも効かせたいもの(証明書ストアやプロキシの設定など)は JAVA_TOOL_OPTIONSと使い分けると整理しやすくなります。Java 25 の公式ドキュメントでも「JDK_JAVA_OPTIONS の内容はコマンドラインから解析されたオプションの前に追加される」と説明されています。

Java 25 での主な違い(公式で確認)

手元には Java 21 しかないため、以下は OpenJDK の JEP と Java 25 の java コマンド公式ドキュメントで確認した内容です(未実行)。

項目 Java 21 Java 25
ZGC の世代別モード -XX:+ZGenerational で有効化(既定は非世代別) Java 23 で世代別が既定(JEP 474)、Java 24 で非世代別モードを削除(JEP 490)。-XX:+UseZGC だけで世代別
コンパクトなオブジェクトヘッダ なし -XX:+UseCompactObjectHeaders が正式機能に(JEP 519)。既定は無効で、1オブジェクトあたり平均4バイト削減と公式に記載
AOT キャッシュ(起動高速化) なし Java 24 で -XX:AOTMode/-XX:AOTCache(JEP 483)、Java 25 で -XX:AOTCacheOutput による1ステップ作成(JEP 514)
-Xmx、-Xlog、HeapDumpOnOutOfMemoryError、UseContainerSupport 書き方は同じ

Java 21 で -XX:+UseZGC -XX:+ZGenerational と書いた起動スクリプトは、Java 25 に上げるときに -XX:+ZGenerational を外すかどうかを公式リリースノートで確認してから移行してください。

よくある質問

Q. -Xms と -Xmx は同じ値にした方がよいですか? A. サーバー用途では同じ値にすることが多いです。起動後のヒープ拡張・縮小がなくなり、動作が安定します。一方、メモリを他のプロセスと分け合う開発機やバッチでは、-Xms を小さくして使った分だけ確保する方が効率的です。

Q. コンテナでは -Xmx と MaxRAMPercentage のどちらを使えばよいですか? A. MaxRAMPercentage がおすすめです。コンテナのメモリ上限を変えたときに自動でヒープも追従します。両方書くと -Xmx が優先されるので、どちらか一方にそろえてください。

Q. GC ログは本番で常に出してよいですか? A. -Xlog:gc* をファイルに出す程度なら負荷は小さく、常に有効にしておく運用が一般的です。filecount と filesize でローテーションを設定し、ディスクを使い切らないようにします。

Q. 今の JVM にどのオプションが効いているか確認するには? A. 起動前なら java <オプション> -XX:+PrintFlagsFinal -version、起動後なら jcmd <pid> VM.flags や jcmd <pid> VM.command_line で確認できます。末尾の {ergonomic} は JVM が自動で決めた値です。

まとめ

  • 最大ヒープの既定はメモリ(コンテナなら上限)の25%。この環境では 5.84GB の25%で約1496MB だった
  • -Xmx と MaxRAMPercentage を両方書くと -Xmx が優先。コンテナでは MaxRAMPercentage が扱いやすい
  • 1 CPU だと GC は Serial が自動選択される。既定の G1 を前提にするなら CPU 数も確認する
  • GC ログは -Xlog:gc*:file=...:time,uptime,level,tags:filecount=5,filesize=10m。Java 8 の PrintGCDateStamps などは Java 21 では起動しない
  • -XX:+HeapDumpOnOutOfMemoryError でダンプは最大ヒープ程度の大きさ(64MB ヒープで約64MB)。出力先の空きを確保する
  • JAVA_TOOL_OPTIONS は全 JVM、JDK_JAVA_OPTIONS は java コマンドだけ。優先順位はコマンドライン>JDK_JAVA_OPTIONS>JAVA_TOOL_OPTIONS
  • Java 25 では ZGC が世代別のみになり、-XX:+ZGenerational は不要

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「JVMオプション一覧|Xmx・Xms・GCログ設定をJava 21対応で」

株式会社RJCでは、JVMオプションの読み方や設定の考え方を仲間と学び合っています。こうした知識を共有しながら、日々技術を磨いています。

株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?