TL

ガベージコレクションの停止時間制御(低レイテンシGC)

ヒープが数百GBでも停止が数ミリ秒で収まる理由が分かる解説。ZGC・Shenandoah・C4のカラーポインタと読み取りバリア、並行コンパクションの原理を正確に押さえる。

応用ガベージコレクション低レイテンシJVMZGC並行GCランタイム最終更新: 2026-06-21
TL;DR要点だけ先に
  • 1.低レイテンシGCは回収の最重量フェーズであるコンパクション(オブジェクト移動)をアプリと並行に行い、STWをルートスキャン等の固定コストへ縮める。
  • 2.カラーポインタは参照アドレスの未使用ビットに移動状態の色を埋め、読み取りバリアが古い参照を検出して新アドレスへ自己修復(self-healing)する。
  • 3.停止時間がヒープ規模にほぼ依存しないのは、STW区間の仕事がヒープサイズではなくルート数とリージョン数に比例するため。

停止時間はどこで生まれるか

トレース型GCの基礎と世代別・並行マークの仕組みはガベージコレクションアルゴリズムの内部で扱いました。本稿はその先、停止時間(pause time)をヒープ規模から切り離すための具体機構に踏み込みます。

GCの一時停止は大きく3つのフェーズから生じます。ルートスキャン(スタックやレジスタの参照を拾う)、マーク(到達可能性の探索)、そしてコンパクション(生存オブジェクトを詰めて断片化を解消する移動)です。このうちマークとコンパクションはヒープに比例して重くなります。G1までの世代別GCは並行マークでマークのSTWを削りましたが、コピー(evacuation)は依然STWで行うため、ヒープが大きく生存量が多いほど停止が伸びます。

低レイテンシGC(ZGC・Shenandoah・Azure C4)の核心は、このコンパクションまでアプリ(mutator)と並行に実行することです。すると問題は「移動の最中にアプリが古いアドレスを掴んでいたらどうするか」という一点に集約されます。これを解くのがカラーポインタと読み取りバリアです。

並行コンパクションの本質的な難しさ

オブジェクトを別のアドレスへ移すと、それを指す全参照を新アドレスへ張り替えねばなりません。STWなら世界が止まっているので一括で書き換えられますが、並行だとアプリが同じ参照を読み書きし続けます。素朴にやれば、移動済みオブジェクトの古いコピーを読むスレッドと新コピーを読むスレッドが混在し、一貫性が壊れます

from/to の二重コピーが共存する瞬間

コンパクションは旧領域(from)から新領域(to)へ生存分をコピーします。並行実行では、ある参照がまだ from を指し、別の参照が既に to を指す、という状態が一時的に必ず生じます。GCはこの過渡状態でも「どのスレッドが見ても同じ最新の値が見える」ことを保証しなければなりません。鍵は、参照を辿る瞬間に必ず最新アドレスへ正規化することです。

カラーポインタ — 参照に状態を埋める

64ビットアドレス空間では、実際に使うアドレスビットは48ビット前後で十分です。カラーポインタ(colored pointer)は、ポインタの未使用ビットにGCのメタデータ(色)を埋め込む手法です。ZGCは下位ビット群に Marked0 / Marked1 / Remapped / Finalizable といった色を持たせ、その参照が「現在のGCサイクルでマーク済みか」「移動先へ張り替え(remap)済みか」を参照値そのものから即座に判定できるようにします。

// ZGC のカラーポインタ(概念図、ビット配置はバージョンで異なる)
[ 未使用 | 色ビット(Marked0/Marked1/Remapped) | 48bitの実アドレス ]

ref を辿るとき:
  if 色 が「現在の良い色」と一致 -> そのまま使える(高速パス)
  else                         -> 読み取りバリアで修復(低速パス)

色を参照に持たせる利点は、オブジェクトのヘッダを書き換えずに状態を表せる点と、判定がポインタのビットマスク比較だけで済む点です。同じ物理オブジェクトを指していても、ビューによって色(=アドレスの別名)が異なる「多重マッピング」を使う実装もあり、ZGCの初期実装はOSの仮想メモリ機能で同一ページを複数アドレスに写像していました(後の世代別ZGCはこれをやめ、ビット比較主体に変更)。仮想アドレス変換の前提は仮想メモリとページングが背景になります。

読み取りバリア — 辿る瞬間に正規化する

世代別GCのwrite barrier(書き込みフック)に対し、低レイテンシGCの主役は読み取りバリア(load barrier)です。ヒープから参照を読み出すたびに短いコードが挿入され、その参照の色を検査します。

// load barrier の概念(参照読み出しをフック)
Object load(field) {
    ref = *field;                  // 生の参照を読む
    if (color_ok(ref)) return ref; // 高速パス: ビット比較のみ
    // 低速パス: 移動済みなら新アドレスへ、未マークならマーク登録
    ref = slow_path(field, ref);   // 必要なら forwarding を辿る
    *field = ref;                  // self-healing: 参照を新値に上書き
    return ref;
}

決定的に重要なのが最後の上書き、**self-healing(自己修復)**です。低速パスで新アドレスを得たら、それを元のフィールドへ書き戻します。すると同じ参照に対する次回以降の読み出しは高速パスを通り、二度と修復が要りません。修復は「触れた参照」にだけ遅延的に課金され、ヒープ全体を一括で書き換える必要がない——これが停止時間をヒープ規模から切り離す要石です。フィールド書き戻しは複数スレッドが競合し得るためアトミックなCAS等で行います(ロックフリーとCAS命令)。

write barrier との役割分担

write barrier は「参照が書き込まれた事実」を記録するのに強く、世代を跨ぐ参照の追跡(remembered set)や並行マークの不変条件維持に使われます。load barrier は「参照を使う瞬間」に正規化するため、並行コンパクションの一貫性を担えます。Shenandoah は当初オブジェクトごとの forwarding ポインタ(Brooks pointer)を使う設計でしたが、後にload barrier主体へ寄せ、ZGC・C4と原理が近づきました。

なぜミリ秒級で止まるのか — 計算量の分解

STW区間に残る仕事を分解すると、なぜ停止がヒープに依存しないかが明確になります。

フェーズ実行モードコストの比例先
ルートスキャン(開始)STW(極小)スレッド数・ルート数
並行マーク並行生存オブジェクト数(停止外)
参照処理・リマップ準備STW(極小)リージョン数・弱参照数
並行コンパクション(移動)並行移動対象量(停止外)

STWに残るのはルートスキャンや弱参照処理といったヒープサイズに対してほぼ定数の仕事だけです。重いマークと移動は並行フェーズに追い出されているため、ヒープが8GBでも数百GBでも、止まる時間は「ルート数とリージョン管理のメタ作業」で決まります。ZGCはこの設計でサブミリ秒〜数ミリ秒、C4(Azul Zing)はTB級ヒープでも一貫した低停止を公称します。停止時間の評価は最悪値(テールレイテンシ)で見るべきで、平均ではなくp99・p999が意味を持つ点も実務上の要点です。

低停止はスループットと帯域を対価にする

load barrier は全参照読み出しに小さなコストを課し、GCスレッドがアプリと同じCPU・メモリ帯域を奪い合います。さらに並行回収が確保に追いつかないと allocation stall(確保の足止め)が起き、最悪はGCが追いつくまでアプリが待たされます。これはSTWではないものの遅延として観測されます。低レイテンシGCは「停止を消す」のではなく「停止をスループット・フットプリント・余剰CPUへ変換する」技術だと理解するのが正確です。

三者の共通原理と相違

ZGC・Shenandoah・C4は実装が異なりますが、並行マーク+並行コンパクション+参照を辿る瞬間の正規化という骨格を共有します。

GC色/転送の持ち方正規化の主機構備考
ZGCカラーポインタ(アドレス内ビット)load barrier + self-healingリージョン(ZPage)単位、世代別版あり
Shenandoah当初Brooks転送ポインタ→load barrierload/参照バリア + 転送OpenJDK、リージョン単位
C4 (Azul Zing)ロード値バリア(LVB)+ ページ転送read barrier + リロケーションTB級ヒープ向け、商用先行

いずれも、参照の有効性をオブジェクトではなく参照値の側で表現し、アクセス時に修復するという発想に収束しています。世代別GCが「いつ・どこを回収するか」で停止を抑えたのに対し、低レイテンシGCは「移動という最重量作業をどう並行化するか」で停止を抑える、というのが分水嶺です。

まとめ

低レイテンシGCは、停止の主因であるコンパクション(オブジェクト移動)をアプリと並行に実行することで成立します。並行移動の一貫性は、参照の未使用ビットに移動状態を刻むカラーポインタと、参照を辿る瞬間に色を検査し古い参照を新アドレスへ書き戻す読み取りバリアのself-healingが担保します。これにより、ヒープ全体の一括書き換えが不要となり、STWに残るのはルートスキャン等のヒープ非依存な定数仕事だけになります。結果として停止時間はヒープ規模からほぼ独立し、数百GB級でもミリ秒級に収まります。ただしこれはバリアコスト・スループット低下・allocation stall という対価の上に成り立つ設計であり、停止・スループット・フットプリントの三角形のどこを取るかという選択であることは、世代別GCから一貫して変わりません。

プログラミング Article

ガベージコレクションの停止時間制御(低レイテンシGC)を実務で読む

TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。

解決すること

ガベージコレクション

比較で見る軸

難易度: advanced / カテゴリ: プログラミング / タグ数: 6

導入後に効く点

カラーポインタは参照アドレスの未使用ビットに移動状態の色を埋め、読み取りバリアが古い参照を検出して新アドレスへ自己修復(self-healing)する。

先に潰すリスク

用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。

数字・仕様の読み方
難易度
advanced
カテゴリ
プログラミング
タグ数
6

判断チェックリスト

  • 自社の用途が「ガベージコレクション / 低レイテンシ」に近いか確認する。
  • 強みである「低レイテンシGCは回収の最重量フェーズであるコンパクション(オブジェクト移動)をアプリと並行に行い、STWをルートスキャン等の固定コストへ縮める。」が本当に評価軸になるか確認する。
  • 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
  • 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
  • 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
  • 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。

次に確認する観点

ガベージコレクション低レイテンシJVMZGC並行GCガベージコレクション低レイテンシJVM