Java

ガベージコレクション(GC)とは?仕組み・アルゴリズム・言語別の違いを解説

ガベージコレクション(Garbage Collection、GC)は、プログラムが使わなくなったメモリ領域を実行環境が自動で見つけて解放する仕組みです。C言語の mallocfree のように開発者が手作業でメモリを返す必要がなくなり、解放漏れ(メモリリーク)や二重解放といったバグを構造的に減らせます。この記事では、GCが「どのメモリをごみと判断するのか」という基本原理から、マーク・アンド・スイープや世代別GCといった代表的なアルゴリズム、Java・Python・C#など言語ごとの違い、そして System.gc() を手動で呼ぶべきかという実務的な論点までを整理します。

まとめ:この記事の要点

  • GCとは:どこからも参照されなくなった(=到達不能になった)オブジェクトを実行環境が自動回収し、メモリを再利用可能にする仕組み。手動メモリ管理のリークと二重解放を防ぐのが目的。
  • 基本原理:GCは「もう使わない」をルート(スタック変数・グローバル変数など)からたどれるか=到達可能性で判定する。到達できないオブジェクトがごみ。
  • 代表アルゴリズム:マーク・アンド・スイープ/参照カウント/コピー/コンパクション/世代別GC。多くの実行環境はこれらを組み合わせる。
  • 言語ごとに違う:Javaは世代別のトレーシングGC(既定はG1)、Pythonは参照カウント+循環参照用の世代別GC、C#/.NETも世代別GC、GoはコンカレントなマークGC。C/C++には原則GCがない。
  • 手動呼び出しは原則不要System.gc()(Java)や GC.Collect()(C#)は基本的に呼ぶべきでない。Pythonの gc.collect() は循環参照を即時回収したい特殊ケースに限る。

ガベージコレクションとは:定義と解決する課題

ガベージコレクションとは、ヒープ上に確保されたオブジェクトのうちプログラムから二度と使われないものを実行環境(ランタイム)が自動的に検出し、そのメモリを解放して再利用できるようにする自動メモリ管理の仕組みです。「ガベージ(garbage)=ごみ」を「コレクション(collection)=回収」することからこの名前が付いています。ガベージコレクタ(GC)と略され、表記のゆれとして「ガーベジコレクション」「ガベコレ」などとも呼ばれますが、すべて同じ概念を指します。

手動メモリ管理との違い:リークと二重解放

GCが登場する前、C言語などでは開発者が malloc で確保したメモリを free で明示的に返す必要がありました。この方式には二つの典型的なバグがあります。ひとつはメモリリークで、解放を忘れると使われないメモリが積み上がり、長時間動くサーバなどでは最終的にメモリ枯渇でクラッシュします。もうひとつは二重解放(ダングリングポインタ)で、すでに解放した領域を再度解放したり参照したりすると未定義動作になります。GCは「不要になったら実行環境が回収する」という方針でこの両方を根本から回避します(メモリリークの詳しい原因と対処はメモリリークとは?原因の特定方法と直し方を参照)。

GCのメリットとトレードオフ

GCの利点はメモリ管理の自動化による安全性と開発効率の向上ですが、無償ではありません。GCはCPU時間を消費し、回収処理のためにアプリケーションのスレッドを一時停止させるStop-the-World(STW)が発生します。停止時間が長いと応答性が落ちるため、低遅延が求められるシステムでは停止を短くするGCの選定やチューニングが必要になります。「自動だから何も考えなくてよい」わけではなく、GCの挙動を理解したうえで大量のオブジェクト生成を避ける設計が、GCの負荷そのものを下げるという点が実務では重要です。

ガベージコレクションの仕組み:到達可能性で「ごみ」を決める

GCの中心的な考え方は到達可能性(reachability)です。実行環境は、確実に生きている参照の起点であるルート集合(スタック上のローカル変数、CPUレジスタ、グローバル/静的変数など)を出発点に、参照をたどれるオブジェクトを「生存」と見なします。ルートからどの経路でもたどり着けないオブジェクトは、プログラムが二度と使えないため「ごみ」と確定し、回収対象になります。

この「参照をたどって生存を判定する」方式を総称してトレーシングGCと呼びます。後述するマーク・アンド・スイープやコピーGC、世代別GCはいずれもトレーシングGCの一種です。一方、参照カウントは各オブジェクトが参照された回数を数える別系統の方式で、両者を組み合わせる言語もあります。

GCアルゴリズムの種類と特徴

ガベージコレクションのアルゴリズムには代表的なものがいくつかあり、それぞれ長所と短所が異なります。実際の実行環境は単一の方式ではなく、これらを組み合わせて使うのが一般的です。

マーク・アンド・スイープ:最も基本的なトレーシングGC

最も古典的で広く使われる方式です。二段階で動作します。マークフェーズでルートから到達可能なオブジェクトすべてに印を付け、スイープフェーズで印の付いていないオブジェクトを解放します。実装が単純で確実ですが、解放後のメモリに空き領域が飛び飛びに残る断片化(フラグメンテーション)が起きやすく、大きな連続領域を確保しづらくなる欠点があります。断片化は後述のコンパクションで解消します。

参照カウント:即時回収できるが循環に弱い

各オブジェクトに「今いくつの参照から指されているか」というカウンタを持たせ、参照が増えれば+1、外れれば-1し、カウントが0になった瞬間に解放する方式です。不要になった直後に回収できSTWが起きにくい反面、二つのオブジェクトが互いを参照し合う循環参照ではカウントが0にならず回収できません。このためPythonのように、参照カウントを主軸にしつつ循環参照専用のトレーシングGCを併用する言語があります。

コピーGC:断片化しないが領域を二分する

ヒープを二つの領域に分け、生存オブジェクトだけを一方からもう一方へ詰めてコピーし、元の領域は丸ごと解放する方式です。生存物を連続配置するため断片化が起きず、確保も速い一方、常に片側を空けておく必要があり実質的に使えるメモリが半分になります。生存率が低い(=すぐ死ぬオブジェクトが多い)領域と相性がよく、後述の世代別GCの若い世代でよく使われます。

コンパクション:断片化を詰め直して解消

コンパクション(compaction)は、生存オブジェクトをメモリの片側に隙間なく詰め直し、連続した空き領域を作る操作です。マーク・アンド・スイープの断片化を解消し、大きなオブジェクトを確保しやすくします。オブジェクトの移動コストがかかるため頻繁には行えませんが、長時間稼働するシステムの断片化対策として有効です。

世代別GC:ほとんどのオブジェクトはすぐ死ぬ

「生成されたオブジェクトの大半は短命で、長く生き残るものは少ない」という世代仮説に基づく方式です。ヒープを若い世代(Young / 新世代)古い世代(Old / 老世代)に分け、頻度と手法を変えて回収します。若い世代を対象とする軽い回収をマイナーGC(Minor GC)、古い世代も含む重い回収をメジャーGC(Major GC)と呼びます。JavaでいうFull GCは、ヒープ全体(新世代・老世代・場合によりメタ領域)を対象にする最も重い回収を指し、頻発すると停止時間が伸びるため避けたいイベントです。若い世代でGCを生き延びたオブジェクトは古い世代へ「昇格」します。

アルゴリズム 断片化 循環参照 回収タイミング 主な使いどころ
マーク&スイープ 起きる 回収可 まとめて 汎用・基礎
参照カウント 起きうる 回収不可 即時 Python・Swift等
コピー 起きない 回収可 まとめて 若い世代
コンパクション 解消する 回収可 まとめて 断片化対策
世代別 実装次第 回収可 世代ごと Java・.NET等

プログラミング言語別のガベージコレクションの違い

GCは言語ごとに実装が大きく異なります。同じ「自動メモリ管理」でも、採用アルゴリズムや手動介入の可否が違う点は、複数言語を扱う開発者がつまずきやすいところです。

言語/環境 主なGC方式 手動トリガ 特徴
Java(JVM) 世代別トレーシング(既定G1) System.gc()※非推奨 G1・ZGC等を選択可
C#(.NET) 世代別(Gen0〜2) GC.Collect()※非推奨 サーバGC/ワークステーションGC
Python(CPython) 参照カウント+循環用世代別 gc.collect() 参照カウントは無効化不可
Go コンカレントなマーク&スイープ runtime.GC() 低遅延を重視
JavaScript(V8) 世代別トレーシング 言語仕様外(Node.jsは--expose-gcglobal.gc()可) Chrome/Node.jsで採用
C/C++ 原則GCなし(手動) RAII・スマートポインタで管理

特徴的なのはPython(CPython)で、参照カウントを常時使いつつ、参照カウントでは回収できない循環参照だけを世代別のトレーシングGC(gc モジュール)が回収します。参照カウントは言語の根幹なので無効化できませんが、循環GCは gc.disable() で止められます。一方Go言語は、停止時間を極小化することを重視したコンカレントなマーク&スイープを採用し、GCとアプリケーションを並行実行します。C/C++はGCを持たず、RAIIや unique_ptrshared_ptr といったスマートポインタで確保と解放をスコープに結び付けて管理します。

JavaのGC実装(G1・ZGC)とチューニング

Javaはガベージコレクションを語るうえで代表的な言語です。JVMはヒープを新世代(Eden+Survivor)と老世代に分け、複数のGC実装から選べます。2026年時点で既定はG1 GC(Java 9以降)で、低遅延用途ではZGCが使われます。ZGCはJava 21で世代別ZGC(JEP 439)が -XX:+ZGenerational により使えるようになり、Java 23以降は世代別が既定です。ヒープサイズによらずミリ秒未満の停止を狙えます(バージョンや既定値は変わるため、利用中のJDKの公式ドキュメントで確認してください)。

// JVM起動オプションの例
java -Xms512m -Xmx2g -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -Xlog:gc App
// -Xms/-Xmx: ヒープの初期・最大サイズ
// -XX:+UseG1GC: G1 GCを使用(-XX:+UseZGC でZGC)
// -XX:MaxGCPauseMillis: 目標とする最大停止時間
// -Xlog:gc: GCログを出力(gcログの有効化)

GCログ(-Xlog:gc)は、いつ・どのGCが・どれだけ停止して・どれだけ解放したかを記録します。停止時間が長い、Full GCが頻発するといった兆候はここで確認し、ヒープサイズやGC種別の調整につなげます。G1・ZGCの選び分けや世代構造、-XX オプションの詳細はJavaのガベージコレクション(GC)の仕組み・種類・チューニングで詳しく扱っています。ヒープの内部構造やサイズ設定、OutOfMemoryErrorの対処はJavaヒープのメモリ構造とサイズ設定を、JVM自体の役割はJava仮想マシン(JVM)とはを参照してください。

手動でGCを呼ぶべきか:System.gc()・GC.Collect()・gc.collect()

「メモリを早く解放したいのでGCを手動で呼びたい」という要望はよくありますが、ほとんどの場合、手動呼び出しは避けるべきです。理由を言語別に整理します。

// Java: 原則呼ばない。あくまで「実行を提案」するだけで強制ではない
System.gc();

// C#/.NET: 原則呼ばない。全世代の重い回収を誘発しやすい
GC.Collect();
# Python: 循環参照を今すぐ回収したい特殊ケースに限る
import gc
gc.collect()

System.gc() は「GCを実行してほしい」という要求にすぎず、JVMが実際に実行する保証はありません。しかも呼ぶと最も重いFull GCを誘発しやすく、本来JVMが最適なタイミングで行う回収を乱してかえって停止時間を増やすことが多いのが実情です。C#の GC.Collect() も同様に全世代回収を招きやすく、通常は不要です。手動呼び出しでメモリリークが直ることはありません。回収されないのは「まだ参照が残っているから」であり、参照を切ること(不要な参照をnullにする、キャッシュを適切に破棄する)が本質的な対処です。

例外はPythonの gc.collect() です。参照カウントで回収しきれない循環参照を、メモリ逼迫前に明示的に回収したい場面や、大量オブジェクト処理の区切りで循環GCを走らせたい場面では意味があります。それでも常用ではなく、プロファイリングで必要性を確認したうえでの限定利用にとどめるのが妥当です。

よくある質問(FAQ)

Full GC(フルGC)とは何ですか?

ヒープ全体(新世代・老世代、実装によってはメタ領域)を対象にする最も重いガベージコレクションです。停止時間が長くなりやすいため、Full GCが頻発している場合はヒープ不足やメモリリーク、GC設定の見直しが必要なサインです。

GCアルゴリズムにはどんな種類がありますか?

基礎となるのはマーク・アンド・スイープ、参照カウント、コピー、コンパクション、世代別GCです。実際のJavaのG1やZGC、.NETのGCなどは、これらを組み合わせて実装されています。

手動でガベージコレクションを実行できますか?

Javaの System.gc()、C#の GC.Collect()、Pythonの gc.collect() で要求できますが、Java・C#では原則非推奨です。特にJavaの System.gc() は実行の保証がなく、停止時間を悪化させることが多いため、通常は実行環境に任せます。

GCログとは何ですか?どう有効化しますか?

GCの発生時刻・種別・停止時間・解放量を記録したログです。Javaでは起動オプション -Xlog:gc(新しいJDK)で有効化し、Full GCの頻度や停止時間の分析、チューニングの根拠に使います。

Pythonにもガベージコレクションはありますか?

あります。CPythonは参照カウントを主軸に、参照カウントでは回収できない循環参照を世代別のトレーシングGC(gc モジュール)で回収する二層構成です。参照カウントは無効化できませんが、循環GCは gc.disable() で停止できます。

関連記事

資料請求

RELATED POSTS 関連記事