「アプリが時々カクつく」「先輩からGCが原因と言われたけど何のこと?」――Javaを学び始めた方や運用担当になったエンジニアからよく聞く相談です。
この記事を読めば、Javaのガベージコレクション(GC)の仕組み・GCアルゴリズム4種類・JVM実装5種類・実践的なチューニング手順まで、5分で全体像をつかめます。System.gc()による手動GCの是非や、G1GC・ZGCといった最新GCの違いまで2026年5月時点の情報で図解付きで解説します。
私自身、10年以上のJava開発でGC起因のパフォーマンス問題に何度も直面し、チューニングで乗り越えてきました。その実体験を踏まえ、最短ルートでGCを理解できるように構成しています。
ガベージコレクションとは?

Javaのガベージコレクション(GC)とは、プログラムが使わなくなったメモリ領域を自動的に見つけて解放する仕組みです。これがあるおかげで、開発者はメモリ管理をJava(JVM)に任せて、アプリケーションのロジック開発に集中できるのです。
Javaにおけるメモリ管理の仕組み
プログラムを動かすには、データやオブジェクトを一時的に保存する場所、つまり「メモリ」が必要です。Javaでは、プログラムが必要なメモリをOSから確保し、その中でオブジェクトを作成したり、不要になったものを片付けたりしています。
この「片付け」を担当するのがガベージコレクションです。例えるなら、優秀な自動お掃除ロボットのようなもの。部屋(メモリ)が散らかってきたら(不要なオブジェクトが増えたら)、自動で掃除(メモリ解放)をしてくれるので、私たちはいつでもきれいな部屋で快適に過ごせ(プログラムを安定して動かせ)ます。
C言語との違い(手動管理との比較)
一方、C言語のようなプログラミング言語では、このような自動お掃除ロボットは存在しません。開発者自身が、メモリの確保(malloc)と解放(free)をコードに明記する必要があります。
これは非常に手間がかかる上、「解放漏れ」や「二重解放」といったメモリ関連のバグを生み出しやすい、とても神経を使う作業でした。解放漏れが起きるとメモリリークとなり、アプリケーションが最終的にクラッシュする原因にもなります。
Javaのガベージコレクションは、このような手動メモリ管理の複雑さや危険性から開発者を解放してくれる、画期的な仕組みなのです。
ただし、「自動だから万能」というわけではありません。GCはアプリケーションを瞬間的に停止させる(Stop The World)副作用を持ちます。そのためミリ秒単位のレイテンシが命のシステムでは「Cのほうが楽だった」と言いたくなる場面も実際にあります。「自動メモリ管理の対価として停止時間というコストを払っている」という認識を持っておくと、後段のチューニングが格段に理解しやすくなります。
Javaガベージコレクションの仕組み

それでは、この優秀なお掃除ロボットは、どのようにして「ゴミ」と「まだ使うもの」を見分けているのでしょうか。その仕組みを3つのステップで見ていきましょう。
ヒープ領域とオブジェクトの寿命
Javaでnewキーワードを使って作られたオブジェクトは、すべて「ヒープ領域」と呼ばれるメモリ空間に格納されます。このヒープ領域が、GCの主戦場、つまりお掃除の対象エリアです。
オブジェクトには「寿命」があります。プログラムのある部分で必要とされて作られても、処理が進むにつれて不要になります。この「不要になった」状態を、GCはどのように判断するのでしょうか。
ヒープの内部構造(Eden・Survivor・Old)
世代別GCで使われるヒープは、さらに細かく分かれています。オブジェクトの寿命に応じて移動していくのがポイントです。
- Eden領域:
newで生成された直後のオブジェクトが置かれる場所。Minor GC で大半が回収される - Survivor領域(S0 / S1): Edenを生き延びたオブジェクトの一時避難所。S0とS1を交互に使ってコピーすることでフラグメンテーションを防ぐ
- Old領域(Tenured): 一定回数のMinor GCを生き延びたオブジェクトが昇格してくる長期居住エリア
このEden→Survivor→Oldという遷移を「世代別ヒープ」と呼びます。短命オブジェクトを高速に回収し、長命オブジェクトはまとめて低頻度で処理することで、全体のオーバーヘッドを抑える設計です。
ルート(GC Root)からの参照関係
GCは、まず「GC Root」と呼ばれる特別な起点から、オブジェクトをたどっていきます。GC Rootは、プログラムが直接アクセスできる場所のことで、例えば実行中のメソッド内にある変数などが該当します。
イメージとしては、家の玄関(GC Root)からスタートして、そこからつながっているすべての家具や物(オブジェクト)を一つひとつ確認していく作業に似ています。玄関からたどって行き着けるものは「まだ使っているもの」と判断されます。
「参照がない=回収対象」となる流れ
GC Rootからたどる作業を行った結果、どこからもたどり着けなかったオブジェクトが出てきます。これが「どこからも参照されていないオブジェクト」、つまり「不要になったゴミ」です。
GCは、このゴミと判断されたオブジェクトが使っていたメモリ領域を解放し、再利用できるようにします。この一連の流れを繰り返すことで、ヒープ領域は常にクリーンに保たれるわけです。
- マーキング: GC Rootからオブジェクトをたどり、生きているオブジェクトに印(マーク)を付ける。
- スイープ(回収): 印が付かなかったオブジェクト(ゴミ)を回収し、メモリを解放する。
このシンプルなルールが、Javaの安定した動作を支えています。
主なGCアルゴリズム4選|マーク&スイープ・コピー・世代別・G1GC

お掃除ロボットにも様々な種類があるように、GCにもいくつかのアルゴリズム(掃除の方法)が存在します。ここでは代表的な4つのアルゴリズムを紹介します。
4つのアルゴリズムを一目で比較
| アルゴリズム | 速度 | メモリ効率 | フラグメンテーション | 主な用途 |
|---|---|---|---|---|
| Mark&Sweep | 普通 | ○ | 発生する | 基本実装・教科書的 |
| Copying | 速い | ×(半分遊ぶ) | 起きない | New領域 |
| 世代別GC | 速い | ○ | 低い | 主流アプローチ |
| G1GC | 速い・予測可能 | ○ | 低い | 大ヒープ・低停止時間 |
Mark and Sweep(マーク&スイープ方式)
Mark and Sweepは最も基本的なGCアルゴリズムで、先ほど説明した仕組みそのものです。
シンプルで分かりやすいですが、掃除後にメモリが虫食い状態になる「フラグメンテーション」が起きやすいという弱点があります。
Copying(コピー方式)
メモリ空間を2つの領域に分け、片方だけを使用します。GCが発生すると、生きているオブジェクトだけをもう一方の空いている領域にコピーします。
コピーが終わると、元の領域はすべてゴミだったことになるので、一気にクリアします。この方式はフラグメンテーションが起きず高速ですが、常に半分のメモリ領域が遊んでいる状態になるため、メモリ効率が悪いのが欠点です。
Generational GC(世代別GC)
多くのアプリケーションでは、「ほとんどのオブジェクトは作られてすぐに不要になる」という統計的な事実があります。この性質を利用したのが世代別GCです。
ヒープ領域を、生まれたばかりのオブジェクトを置く「New(Young)領域」と、何度もGCを生き延びたオブジェクトを置く「Old領域」に分けます。
このハイブリッドなアプローチにより、アプリケーション全体の停止時間を短くし、効率的なメモリ管理を実現しています。2026年5月時点でも、G1GCやGenerational ZGCをはじめとする主要GCに採用されている主流の考え方です。
G1GC(Garbage First GC)
G1GCは、ヒープ領域を多数の小さな「リージョン」に分割して管理します。そして、ゴミが最も多く溜まっているリージョンから優先的(Garbage First)に掃除を行います。
大きなヒープ領域でも予測可能な短い停止時間を実現できるように設計されており、Java 9以降のデフォルトGCとして広く使われています。2026年5月時点でも、G1はJDK 25で改良が続けられており(remembered setのメモリ効率改善等)、汎用ワークロードのファーストチョイスです。
JavaのGC実装5種類|Serial・Parallel・CMS・G1GC・ZGCの違い

これまで見てきたアルゴリズムをベースに、Java(JVM)にはいくつかのGC実装が用意されています。バージョンアップと共に、より高性能なGCが登場してきました。
Serial GC
シングルスレッドでGCを行う、最もシンプルな実装です。GC中はアプリケーションが完全に停止(Stop The Worldと呼ばれます)します。CPUコアが1つしかないような小規模な環境や、クライアントPC上の単純なアプリケーション向けです。
Parallel GC
複数のスレッドを使ってGC処理を並列実行することで、GC時間を短縮します。特にNew領域のGC(Minor GC)を高速に行うのが得意です。Java 8までのデフォルトGCで、スループット(単位時間あたりの処理量)を重視するバッチ処理などに向いています。Java 9以降はG1GCがデフォルトとなり、現在では特殊なスループット重視ワークロード以外で積極的に選ぶ理由は減っています。
CMS(Concurrent Mark-Sweep)GC
アプリケーションの停止時間(Stop The World)を極力短くすることに重点を置いたGCです。マークやスイープといった処理の一部を、アプリケーションスレッドと同時に(Concurrent)実行しようと試みます。応答速度が求められるWebアプリケーションで利用されていましたが、Java 9でDeprecated(非推奨)となり、Java 14(2020年)で完全に削除されました。新規プロジェクトでの選択肢にはなりません。
G1GC
前述の通り、ヒープをリージョンに分割して管理するGCです。スループットと応答時間のバランスが良く、Java 9からデフォルトのGCとなりました。数GBから数十GBといった比較的大きなヒープサイズを扱う現代的なアプリケーションに適しています。
ZGC(最新のGC方式)
ZGCは、Stop The Worldを極限まで短くすることを目指したGCです。Java 21でGenerational ZGC(世代別ZGC)が追加され、Java 24では非世代モードが削除、Java 25(2025年9月リリースのLTS)以降はGenerational版のみが有効です。数TB(テラバイト)級の巨大なヒープサイズでも停止時間をサブミリ秒に抑えられます。一方で、G1GCに比べてメモリを15〜30%、CPUを5〜10%多く消費する点は留意が必要です。応答性能が非常にクリティカルでリソースに余裕がある大規模システム向けのGCです。
実務でのGC選定基準|筆者が現場で使い分ける判断軸
5種類のGCを実装上知っているだけでは現場では使えません。10年以上のJava開発で、私が実際にGCを選ぶときに使っている判断軸はシンプルです。
- ヒープ4GB未満・スループット重視のバッチ:
-XX:+UseParallelGC。CPUを使い切ってでも処理時間を縮めたいときの選択。 - ヒープ4〜32GB・汎用Webアプリ: G1GC(Java 9+のデフォルト)。何も指定しないが正解の領域。
- ヒープ32GB超・低レイテンシSLO(p99 100ms未満):
-XX:+UseZGC(Generational ZGC)。CPU・メモリのオーバーヘッドを許容できるなら最有力。 - コンテナで<512MB: Serial GC(
-XX:+UseSerialGC)。マイクロサービスのCPU/メモリ食い荒らしを防ぐ目的でJVMオプションに固定する。
「最新だからZGC」「速いからParallel」と単独要素で選ぶのは危険です。本番投入後にp99レイテンシ悪化やコンテナOOMKillに悩まされます。ヒープサイズ × SLO(停止時間/スループット)× リソース余裕の3軸で決めるのが結局いちばん事故が少ない、というのが現場の感覚です。
GCチューニング実践|ヒープサイズ調整と手動GC(System.gc())の使い方

GCは自動で動きますが、時にはアプリケーションの特性に合わせて設定を調整(チューニング)することで、パフォーマンスが劇的に改善することがあります。
GCの動きを見る
まずは、GCがどのように動いているかを知るのが第一歩です。Javaの起動オプションに以下を追加すると、GCの動作がログに出力されます。
-Xlog:gcこのログを見ることで、GCの発生頻度や、一回のGCにかかった時間、メモリの解放量などを確認できます。パフォーマンス問題の調査には不可欠な情報です。Java 9以降の統一ロギング(Unified Logging)形式で、詳細を出すなら -Xlog:gc*=info、ファイル出力なら -Xlog:gc*:file=gc.log のように指定します。
メモリサイズを調整する
最も基本的で効果的なチューニングが、ヒープサイズの設定です。
-Xms<size>: ヒープ領域の初期サイズ-Xmx<size>: ヒープ領域の最大サイズ
例えば、初期サイズを1,024MB、最大サイズを2,048MBに設定する場合は以下のようになります。
-Xms1024m -Xmx2048m一般的に、サーバーアプリケーションでは-Xmsと-Xmxを同じ値に設定することが推奨されます。実行中にヒープサイズが変動する際のオーバーヘッドをなくし、パフォーマンスを安定させることができます。
手動GC(System.gc())は使うべき?
Javaには System.gc() を呼び出してGCを明示的に要求する方法がありますが、通常のサーバーアプリケーションでは推奨されません。理由は3つあります。
- あくまで「要請」:
System.gc()はJVMにGCの実行を依頼するだけで、即時実行は保証されません。 - Stop The Worldを誘発: 自動GCが最適化したタイミングを乱し、応答性能を悪化させます。
- 不要な負荷: 多くの場合JVMの方が賢く判断するため、人間の介入は逆効果になります。
例外的に有効なのはバッチ処理の境界やヒープダンプ取得直前などの限定用途です。サービスでの濫用を防ぐには、JVMオプション -XX:+DisableExplicitGC で System.gc() を無効化する選択肢もあります。
チューニングの基本的な考え方
GCチューニングは奥が深い世界ですが、基本はシンプルです。
- 現状把握: まずはGCログを取得し、現状のGCの振る舞いを正確に把握します。
- ボトルネック特定: GCの時間が長すぎるのか、それとも頻度が多すぎるのか、問題点を特定しましょう。
- 仮説と検証: ヒープサイズやGCアルゴリズムの変更といった対策を立て、実際に試して効果を測定します。
やみくもな設定変更は、かえって状況を悪化させることもあります。根本的な解決策は、不要なオブジェクトの生成を抑えるプログラムを書くことである、という点も忘れてはいけません。
GCチューニングでハマりがちな3つの落とし穴
「とりあえずヒープを大きくする」「ZGCにすれば解決する」――10年以上の現場経験で何度も見かけたアンチパターンを3つ紹介します。
- ヒープを増やしてもFull GCが減らない: 原因は
staticMapやキャッシュへのオブジェクト溜め込み(メモリリーク)。jcmd <pid> GC.heap_dumpで確保元を確認するのが先決。 - ZGCに切り替えたらレイテンシは改善したがスループットが落ちた: ZGCはCPUオーバーヘッドが5〜10%増える。バッチ系・CPUバウンドなワークロードでは逆効果。SLOがレイテンシ寄りかスループット寄りかで選ぶ。
- コンテナでJVMがメモリを食い潰してOOMKill:
-Xmxをコンテナのメモリ上限と同じに設定するとMetaspaceやスレッドスタックで超過。コンテナ上限の70〜75%を目安に-Xmxを設定するのが鉄則。
どれも「GCログを見ずに勘で設定を変えた」ときに起きます。-Xlog:gc* でログを取り、GCeasy のような可視化ツールで分析するワンステップを挟むだけで、ほとんどの事故は防げます。
Javaガベージコレクションのよくある質問(FAQ)
GCはいつ実行されますか?
JVMがヒープ使用率や残メモリ量から自動判断します。Eden領域が満杯になればMinor GC、Old領域の使用率がしきい値を超えればMajor GC(Full GC)が走ります。System.gc() による明示制御は非推奨です。
Full GCとMinor GCの違いは?
Minor GCはNew(Young)領域のみを対象とし、短時間で完了します。一方Full GCはOld領域・Metaspaceを含む全ヒープを対象とし、停止時間(Stop The World)が秒単位になることもあります。Full GC が頻発するアプリは要チューニング対象です。
Java 21以降のおすすめGCは?
2026年5月時点で、汎用WebアプリはG1GC(デフォルト)、低レイテンシ・大ヒープ要件ならGenerational ZGC(Java 21+)が第一候補です。Java 17以降ではParallel GCやCMSは特殊用途以外で選ぶ必要はほぼありません。
OutOfMemoryErrorはGCの問題ですか?
多くはメモリリーク(参照を握り続けている)かヒープサイズ不足が原因で、GC自体の不具合ではありません。-Xmx の見直しと、jmap・jcmd でのヒープダンプ解析がまず行うべき切り分け手順です。
まとめ
この記事では、Javaのガベージコレクションの基本的な仕組みから、アルゴリズムの種類、そして実践的なチューニングの第一歩までを解説しました。
Java GCを理解する3つのメリット
JavaのGCを理解することは、エンジニアにとって大きな力になります。
- パフォーマンス問題の原因究明: アプリケーションが遅い時、GCが原因かどうかを切り分けられるようになります。
- メモリ効率の良いコード: GCの仕組みを知ることで、メモリに優しい、より効率的なコードを書く意識が芽生えます。
- 適切なJVM設定: アプリケーションの特性に合わせた、適切なヒープサイズやGCアルゴリズムを選択できるようになるでしょう。