「java gc」で調べてFull GCの記事にたどり着いたものの、そもそもGCが何をしているのか曖昧なまま、という方は多いはずです。
Javaのガベージコレクション(GC)とは、どこからも到達できなくなったオブジェクトのメモリを、JVMが自動で回収する仕組みです。
回収は「世代」ごとに行われ、短命なオブジェクトが集まるYoung領域はこまめに、長生きするオブジェクトが集まるOld領域はたまに掃除します。
ヒープ全体を一度に片づける最も重い回収がFull GCで、生きているオブジェクトの多い大きなヒープでは、アプリが数百ミリ秒から秒単位で止まる原因になります。
この記事では、定義、世代別の仕組み、GCの種類、Full GCの原因7つ、GCログの読み方の順に、図とログで説明します。
この記事でわかること
- GCの定義と、回収される・されないの境目
- Young領域・Old領域、Minor GC・Full GCの違い
- Serial・Parallel・G1・ZGCの違いと、JDKの版ごとの既定
- Full GCが起きる原因7つと、ログで見分ける方法
動作は Amazon Corretto 21.0.5(G1・Serial・Parallel・ZGC)と OpenJDK 23.0.1(ZGC)で確認しました(2026年10月)。
記事中のGCログは、JDK 21.0.5で最小のコードを動かして取った実際の出力です。
既定値やオプションの説明は、OracleのGCチューニングガイドとOpenJDKのJEPに沿っています。
PARTS LIST /目次
ガベージコレクション(GC)とは?Javaが不要なメモリを自動で片づける仕組み
ガベージコレクションとは、プログラムから到達できなくなったオブジェクトのメモリを、JVMが自動的に回収して再利用できるようにする仕組みです。
Javaでは new でオブジェクトを作るだけで、解放の処理は一行も書きません。
その代わり、いつ・どれだけ回収するかはJVMが決め、回収中はアプリの処理が止まることがあります。
この「自動で楽な代わりに、止まるタイミングを自分で決められない」という性質が、GCの理解で最初に押さえる点です。
回収されるのは「到達できない」オブジェクト
GCは「使い終わったかどうか」ではなく、「プログラムの出発点から参照をたどって届くかどうか」で回収対象を決めます。
出発点はGC Rootと呼ばれ、実行中スレッドのスタック上のローカル変数、staticフィールド、JNIの参照などが入ります。
GC Rootからたどれるオブジェクトは生きているとみなされて残り、たどれないオブジェクトは回収されます。
参照がなくなったかどうかを人間が判定する必要はありません。
下の図は、GC Rootからの到達可能性で生死が分かれる様子です。
参照カウント方式のように「循環参照で残り続ける」ことはありません。
逆に、不要なオブジェクトでもstaticなリストなどから参照され続けていれば、GCは回収できません。これがメモリリークの正体で、後半で再現します。
オブジェクトの置かれる場所は、ヒープ・スタック・参照の関係として図で整理した記事があります。
先に全体像をつかみたい場合は、図で動かすJVMメモリから読むとこの記事の図が追いやすくなります。
C言語の手動解放とJavaのGCは何が違う?
C言語では malloc で確保したメモリを free で自分で返します。
Javaは回収をJVMに任せるので、返し忘れによるリークと二重解放によるクラッシュを、言語の仕組みとして避けられます。
Javaで消えるのは「解放を書き忘れる事故」であって、メモリの問題そのものではありません。
参照を握り続けるコードを書けば、GCがあってもメモリは減り続けます。
JavaのGCの仕組み|世代別ヒープとMinor GC・Full GCの流れ
JavaのGCは、ヒープをオブジェクトの年齢で領域に分け、若い領域を頻繁に、古い領域をまれに回収する「世代別」の設計が基本です。
Oracleのガイドは、この設計の根拠を「大半のオブジェクトは短命で、生成後すぐに不要になる」という経験則に置いています。
若い領域だけを回収すれば大半のゴミが片づくので、ヒープ全体を毎回なめるより効率がよくなります。
ここでは、領域の分け方、オブジェクトの一生、回収の種類を順に見ます。
ヒープはYoung領域とOld領域に分かれる
世代別GCのヒープは、新しいオブジェクトを置くYoung領域と、長く生き残ったオブジェクトを置くOld領域に分かれます。
Young領域はさらに、生成直後のオブジェクトが入るEdenと、生き残りを一時的に置く2つのSurvivor領域で構成されます。
次の図は、new で作られたオブジェクトがどう移動して、どこで回収されるかを追ったものです。
new で生成-XX:MaxTenuringThreshold で決まります(手元の JDK 21 の既定値は15)。Survivor領域に収まりきらない場合など、閾値に届く前にOldへ送られることもあります。Eden→Survivor→Oldの移動を昇格(promotion)と呼びます。
Minor GCを1回生き残るたびにオブジェクトの年齢が1増え、閾値に達するとOld領域へ昇格します。
G1・ZGCなどの新しいGCは、ヒープを固定の連続領域ではなく多数の小さなリージョンに分けて管理します。
概念としてのYoung・Oldは残りますが、境界が固定でない点は頭に置いてください。
Minor GC・Mixed GC・Full GCの違い
GCの呼び名は「どこを回収するか」で分かれます。
Oracleのガイドでは、Young領域だけを対象にした回収がminor collection、ヒープ全体を対象にした回収がfull collectionです。
G1にはこの中間にあたるMixed GCがあります。
「Major GC」という語も見かけますが、Old領域だけの回収を指すこともFull GCの別名として使われることもあり、定義が揃っていません。
ログや議論では、GCの種類と原因ラベルを確認するほうが確実です。
Stop-The-Worldとは?アプリが止まる理由
Stop-The-World(STW)は、GCが安全に参照関係を調べたり、オブジェクトを移動したりするために、アプリのスレッドをすべて一時停止する状態です。
オブジェクトを移動している最中にアプリが参照を読み書きすると、古い場所を指す参照が残って壊れてしまうため、止める必要があります。
STWの長さは、回収する範囲とGCの実装で決まります。
- Minor GC:Young領域だけなので、数ミリ秒程度で済むことが多い
- Full GC:ヒープ全体が対象なので、ヒープが大きいほど長い
- ZGCのような並行型:作業の大半をアプリと並行して進め、停止を短く保つ設計
GCの性能は「どれだけ回収できるか」より、「アプリがどれだけ止まるか」で比べられます。
GCの種類と選び方|Serial・Parallel・G1・ZGCの違い
HotSpot JVMには複数のGCが入っていて、-XX:+UseG1GC のように起動オプションで切り替えます。
どのGCも「不要なオブジェクトを回収する」役割は同じですが、止まる時間を縮めたいのか、処理量を最大にしたいのかで設計が違います。
先に結論を書くと、特別な理由がなければ既定のまま使い、停止時間が問題になってから見直すのが順当です。
下の比較は、Oracleの公式ガイドの記述をもとにまとめています。
4つのGCを一覧で比べる
ZGCの停止時間は、公式ガイドで1ミリ秒未満が目標とされ、ヒープサイズに左右されない設計です。
世代別ZGCはJDK 21でJEP 439として入り、-XX:+ZGenerational で有効にします。JDK 23(JEP 474)で既定が世代別になり、JDK 24(JEP 490)で非世代別モードが削除されました。
JDK 21で -XX:+UseZGC だけを指定すると非世代別ZGCになる点に注意してください。
OpenJDKにはShenandoahもあります。JEP 379で本番利用が可能になりましたが、どのビルドに含まれるかはディストリビューション次第です。
バッチ処理のようにスループット優先の処理では、Parallelが候補に入ります。
バッチの設計そのものは、Javaバッチ処理の実装方法にまとめています。
JDKの版で既定のGCは変わる
「Javaの既定のGCはどれ?」は、JDKの版とマシンの条件で答えが変わります。
JDK 8までの既定はParallelで、JDK 9のJEP 248でG1に変わりました。ただし条件があり、JVMは2CPU以上かつ物理メモリ1792MB以上のマシンを「サーバークラス」とみなしてG1を選び、それ未満ならSerialを選びます。
コンテナで見落としやすい点
CPUを1つまたはメモリを小さく制限したコンテナでは、サーバークラスの条件を満たさずSerialが選ばれます。手元のJDK 21で -XX:ActiveProcessorCount=1 を付けて起動すると、実際に Using Serial と出ました。
使っているJDKの版は java -version で分かります。確認コマンドはJavaバージョン確認コマンド一覧にOS別でまとめています。
起動時に -Xlog:gc を付ければ、先頭行の Using G1 のような表示で、実際に選ばれたGCを確認できます。
CMS(Concurrent Mark Sweep)は、JDK 9のJEP 291で非推奨になり、JDK 14のJEP 363で削除されました。
古い記事の -XX:+UseConcMarkSweepGC は、いまのJDKでは使えません。
どのGCを選べばよいか
選び方は、次の順で絞れば迷いません。
- 既定のGC(G1)で、停止時間が問題になっていなければ変えない
- 小さなヒープ・1CPUのコンテナなら、Serialが選ばれて当然と考える
- 1秒以上の停止が許されるバッチ処理なら、Parallelを検討する
- 停止時間をミリ秒未満に抑えたいなら、ZGCを検討する
どの場合も、変更の前後でGCログを取り、停止時間とスループットを比べてから決めます。
GCの種類を変える前に、ヒープサイズと保持しているオブジェクトの量を見直すほうが先です。後半のログの例でも、原因はGCの種類ではなく参照の持ち方にありました。
Full GCとは?起きる原因7つとログでの見分け方
Full GCとは、ヒープ全体を対象にして、不要なオブジェクトの回収と圧縮(コンパクション)をまとめて行う、停止時間の最も長いGCです。
G1では、通常の回収で空きを確保できなかったときの最後の手段として、ヒープ全体を停止したまま圧縮する処理が走ります。Oracleのガイドもこの処理を「非常に遅くなりうる」と説明しています。
Full GCが1回起きること自体は異常ではありませんが、短い間隔で何度も起きるなら、原因を調べる合図です。
Full GCの原因7つとログの手がかり
原因を切り分ける近道は、GCログに出る原因ラベルです。
ログの括弧内に、System.gc() や Metadata GC Threshold のような原因が書かれます。下の表は、手元の JDK 21 でそれぞれの原因を再現して確認したラベルです。
メモ:Metaspaceとは
クラスのメタデータ(クラス名・メソッド情報など)を置く領域です。JDK 8でPermGenに代わって導入され、ヒープとは別に管理されます。
表の7つを、順に補足します。
1と3は、ヒープの大きさとオブジェクトの寿命の問題です。Oldが埋まってFull GCが走る流れは単純ですが、Youngが小さすぎると、本来すぐ不要になるオブジェクトが昇格してOldを無駄に埋めます。
2は、GCでは直せない問題です。Full GCを繰り返しても使用量が下がらないなら、回収できない参照が残っています。
4のMetaspaceは、クラスのメタデータを置く領域です。-XX:MetaspaceSize に達するとクラスのアンロードを狙ったGCが走ります。
手元で動的にクラスを大量に作るコードを動かすと、Parallelでは Pause Full (Metadata GC Threshold) が出ました。同じコードでもG1では、このラベルがYoung GCに付きました。GCの種類でFull GCになるかが変わる点に注意が必要です。
5は、コードやライブラリが System.gc() を呼ぶケースです。RMIを使うアプリでは、RMIの分散GCが定期的に System.gc() を呼びます。間隔は sun.rmi.dgc.client.gcInterval と sun.rmi.dgc.server.gcInterval で変えられます。
6は意外に見落とされます。jmap -histo:live や jcmd <pid> GC.class_histogram は、生きているオブジェクトだけを数えるために先にGCを走らせます。本番で調査コマンドを叩いたタイミングでFull GCが出るのは、このためです。
7はG1の挙動です。退避先の空きが足りないとき、またリージョンの半分以上の大きさの巨大オブジェクト(Humongous)を置く連続領域が取れないときに、Full GCに落ちます。
実際にFull GCを起こしてみたログ
メモリリークを最小のコードで再現して、ログを取ります。staticなリストに1MBの配列を入れ続け、合間に一時オブジェクトも作る単純なコードです。
import java.util.ArrayList;
import java.util.List;
public class LeakDemo {
// 参照を持ち続けるstaticなリスト(これがリークの正体)
static final List<byte[]> keep = new ArrayList<>();
public static void main(String[] args) {
while (true) {
keep.add(new byte[1024 * 1024]); // 1MBを保持し続ける
for (int i = 0; i < 20; i++) {
byte[] temp = new byte[100 * 1024]; // すぐ不要になる一時オブジェクト
}
}
}
}
ヒープを256MBに絞り、GCログを出して実行します。
java -Xmx256m -Xlog:gc LeakDemo
JDK 21.0.5(G1)では、次のようなログが出ました(抜粋)。
[0.003s][info][gc] Using G1
[0.016s][info][gc] GC(0) Pause Young (Normal) (G1 Evacuation Pause) 45M->23M(256M) 0.382ms
[0.035s][info][gc] GC(1) Pause Young (Concurrent Start) (G1 Humongous Allocation) 207M->115M(256M) 0.474ms
[0.053s][info][gc] GC(14) Pause Full (G1 Compaction Pause) 252M->251M(256M) 0.950ms
[0.054s][info][gc] GC(18) Pause Full (G1 Compaction Pause) 253M->253M(256M) 0.848ms
[0.056s][info][gc] GC(19) Pause Full (G1 Compaction Pause) 253M->253M(256M) 1.389ms
Full GCの行で、252M->251M(256M) と使用量がほとんど減っていません。
回収できるものがなく、満杯のヒープで空き待ちのまま、Full GCが連続しています。このあと OutOfMemoryError で落ちます。
Full GCの前後で使用量が下がらないのは、GCの設定ではなく、参照を持ち続けているコードの問題です。ヒープを増やしても、落ちるまでの時間が延びるだけです。
System.gc() を3回呼ぶだけのコードでは、次の行が出ます。
[0.014s][info][gc] GC(0) Pause Full (System.gc()) 2M->1M(14M) 1.292ms
ヒープがほぼ空でも、System.gc() はFull GCを起こします。原因ラベルが System.gc() と出るので、一目で見分けられます。
Full GCが頻発するときの調べ方
Full GCが続いたら、原因ラベルから調べる方向を決めます。
ヒープ使用量が下がるかどうかで、リークの疑いとサイズ不足を分けられます。
System.gc() / Heap Inspection Initiated GCMetadata GC ThresholdErgonomics / Allocation Failure / G1 Compaction Pauseリークが疑われるときのために、OutOfMemoryErrorでヒープダンプを自動出力する設定を入れておくと、後から保持元を調べられます。
java -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/app/ -jar app.jar
このオプションは既定で無効で、有効にするとOutOfMemoryErrorの発生時にヒープがファイルに書き出されます。ヒープが大きいとファイルも大きくなるので、出力先の空き容量を確認してください。
GCログの読み方とチューニングの入口
GCの調整は、数値を測ってから行うのが原則です。
勘でオプションを足すと、かえって停止時間が延びることがあります。
JDK 9以降は統一ログの -Xlog でGCの動きを記録できるので、まずログを出す設定から始めます。
GCログの1行から読み取れること
JDK 9以降の統一ログは、1行にいくつかの情報が並びます。
[0.053s][info][gc] GC(14) Pause Full (G1 Compaction Pause) 252M->251M(256M) 0.950ms
見る順番は、種類と原因、GC後の使用量、停止時間です。
Full GCの頻度と停止時間が、アプリの応答時間の悪化と重なっているかを、時刻で突き合わせます。
ファイルに出すなら、次のように書きます。
java -Xlog:gc*:file=gc.log:time,level,tags -jar app.jar
-Xlog:gc* は、GC関連のログをまとめて出す指定です。
詳しい出力が不要なら、-Xlog:gc だけで1回ごとの要約が得られます。
ヒープサイズを見直す
ヒープの最小・初期サイズは -Xms、最大サイズは -Xmx で指定します。
指定しない場合、最大は物理メモリの1/4、初期は1/64が既定です。コンテナでは、JVMに割り当てられたメモリが基準になり、-XX:MaxRAMPercentage の既定は25%でした。
java -Xms512m -Xmx512m -jar app.jar
ヒープを増やせばFull GCが減るとは限りません。
リークならゼロ回に近づくどころか、落ちるまでが長くなり、1回のFull GCも長くなります。
サイズ不足だと判断できる根拠(GC後の使用量が下がる、ヒープに対して常用量が大きい)があるときに増やします。
System.gc()は呼んでよいか
Oracleのガイドは、System.gc() について、本来必要のないタイミングで大規模な回収を強制しうるので一般に避けるべきとしています。
呼び出しの影響を測りたいときは、-XX:+DisableExplicitGC で呼び出しを無視させて比べます。
自分のコードに System.gc() を書く場面は、まずありません。
ライブラリやRMI由来で呼ばれていることはあるので、ログに Pause Full (System.gc()) が出たら、呼び出し元を探すのが先です。
GCでは直らないメモリリークとOOM Killer
GCは、到達できないオブジェクトしか回収できません。
次のような書き方は、不要になったオブジェクトを参照し続けます。
- staticなコレクション(
List・Map)に追加するだけで、削除しない - 上限のないキャッシュ
- 登録したリスナーやコールバックを解除しない
ThreadLocalに入れたまま、スレッドプールのスレッドを使い回す
ログの使用量が右肩上がりなら、ヒープダンプを取って、どのオブジェクトが多いかを調べます。
JavaのOutOfMemoryErrorと、OSによるプロセスの強制終了(OOM Killer)は別物です。
前者はJVMが例外を投げて検知でき、後者はヒープの外を含めてプロセスのメモリがOSの上限を超えたときに起き、Javaのログに何も残らないことがあります。
私がNginxとJavaの連携で出る502 Bad Gatewayを調べたとき、原因の多くはJavaアプリの起動失敗で、OOM Killerもその一つでした。切り分けはJavaとNginxの連携方法に書いています。
例外のスタックトレースの読み方は、Javaの例外とエラーの解説が参考になります。
よくある質問
Java GCについて、読者からよく聞かれる質問をまとめました。
Q1. GCはいつ実行されますか?
A1. JVMが必要と判断したときに実行されます。Eden領域が埋まるとMinor GC、Old領域やMetaspaceの空きが足りなくなるとMixed GCやFull GCが動きます。System.gc() の呼び出しや診断コマンドで起きることもあり、開発者が実行時刻を決めることはできません。
Q2. Minor GCとFull GCの違いは何ですか?
A2. 回収範囲が違います。Minor GCはYoung領域だけを対象にして短時間で終わり、Full GCはヒープ全体を対象にして停止時間が最も長くなります。通常はMinor GCが頻繁に、Full GCはまれに起きるのが健全な状態です。
Q3. Javaの既定のGCは何ですか?
A3. JDK 9以降は、2CPU以上かつ物理メモリ1792MB以上の環境でG1、それ未満の環境でSerialです。JDK 8まではParallelでした。JDK 27以降は、JEP 523により環境を問わずG1になります。
Q4. System.gc()は呼んでもよいですか?
A4. 通常は呼びません。Oracleのガイドも一般に避けるべきとしており、ヒープがほぼ空でもFull GCが走ります。ログに Pause Full (System.gc()) が出るなら、呼び出し元を探して取り除くのが先です。
Q5. OutOfMemoryErrorはGCの問題ですか?
A5. GCが回収しようとしても空きを作れなかった結果なので、多くはGCの設定でなくコードの問題です。参照を持ち続けるリークか、ヒープサイズの不足が原因になります。OSのOOM Killerによる終了は別の現象です。
Q6. ZGCに変えればFull GCやStop-The-Worldの問題は解決しますか?
A6. 停止時間は大きく縮められますが、リークは直りません。ZGCも回収できない参照は回収できず、最終的にOutOfMemoryErrorになります。JDK 21では -XX:+UseZGC だけだと非世代別になるので、-XX:+ZGenerational の有無も確認してください。
Q7. ヒープサイズを大きくすればGCの問題は解決しますか?
A7. 解決するとは限りません。サイズ不足が原因ならFull GCは減りますが、リークが原因なら落ちるまでの時間が延びるだけです。Full GC後に使用量が下がるかを、ログで確認してから増やします。
まとめ:GCを読み解く5つのポイント
GCの理解は、「何が回収され」「どこで止まり」「何がFull GCを呼ぶか」の3点に集約できます。
ここまでの内容を、実務で使う順に並べ直します。
【要点まとめ】
- GCは、GC Rootから到達できないオブジェクトを自動で回収する仕組み
- 世代別GCは、Young領域をこまめに、Old領域をまれに回収する
- 既定はJDK 9以降でG1(条件を満たさない環境ではSerial)、JDK 27以降はG1に統一
- Full GCの原因は、ログの括弧内の原因ラベルで絞り込める
- Full GC後に使用量が下がらないなら、GCの設定でなくコードのリークを疑う
次にやることは、手元のアプリに -Xlog:gc を付けて起動し、原因ラベルとGC後の使用量を見ることです。
Full GCの行が1本も出ないなら、いま調整する必要はありません。
| REV. | DATE | 内容 |
|---|---|---|
| 5 | 2026-10-21 | 同じ説明の繰り返しを減らし、読みやすく整えた |
| 4 | 2026-10-15 | 図解を描き直し(図面のデザインにそろえた) |
| 3 | 2026-08-01 | 2026年8月時点の情報で全体を見直し(古い記述を5件更新) |
| 2 | 2026-05-06 | 2026年5月時点の情報で全体を見直し(古い記述を5件更新) |
| 1 | 2025-09-23 | 初版 |

