한국어English日本語简体中文繁體中文DeutschไทยTiếng ViệtРусскийPortuguês (Brasil)EspañolBahasa Indonesia

ゲームラグ白書 › L10 メモリ

サーバーGCの全停止 Stop-the-world GC pause

原因ID mem-gc · 主担当 ゲーム開発チーム・サーバー開発 · 副担当 インフラチーム・サーバーインフラ

図と実験のあるメインページでこのカードを開く →

Java・C#のサーバーがガベージを回収するために全スレッドを止めている間(stop-the-world)、サーバー全体が止まります。

なぜ ヒープが埋まってGCが始まる → すると 全ゲームスレッドを止めて回収(生存データが多いほど長引く) → 画面では サーバー上の全員が同時にフリーズし、その後早送り

症状
フリーズ, 早送り
要因
ストール
誰に起きるか
サーバー全体
いつ
一定の周期で, 長時間稼働するほど
担当
主担当 ゲーム開発チーム・サーバー開発 · 副担当 インフラチーム・サーバーインフラ
ゲーム開発チームの対応
停止時間の短いGC(ZGC、Shenandoah、目標時間を短くしたG1)を起動オプションで明示的に指定、アロケーションの削減、ヒープサイズの調整。
インフラチームの対応
ヒープを十分に確保できるメモリ量のインスタンス、コンテナにはCPUを2個以上・メモリを約1.8GB以上割り当て(JDK 26以前はそれより小さいとSerial GCがデフォルトで選ばれる)、GC停止時間の監視。
数値の目安
新しいオブジェクト(Young領域)だけを回収するMinor GCは数〜数十ms。生存データが数GBあるヒープ全体を回収するFull GCは1秒を超えることもあります。ZGCはヒープサイズにほとんど関係なく1ms未満で、Shenandoahも停止時間がヒープサイズに比例しないため短く済みます。
グラフでは
周期的なスパイク · サーバーのティック時間、GC停止時間
確認箇所
GCログを有効にし、停止した時刻と長さをサーバーのティック時間グラフに重ねて確認。Javaは起動オプション-Xlog:gc*(JDK 8以前は-XX:+PrintGCDetails)のPause行、.NETはdotnet-countersのGC停止メトリクス(.NET 9以降はdotnet.gc.pause.time、8以前は% Time in GC since last GC)、GoはGODEBUG=gctrace=1がGCごとに出力する行を確認
該当する場合
ティックが跳ねた時刻とGC停止の時刻が重なり、停止の長さが跳ねた長さとほぼ同じ。サーバーのすべてのゾーン・チャンネルが同じ瞬間に跳ねる
該当しない場合
GCログに長い停止がないのにティックが跳ねるなら、ロック・同期呼び出し・ディスク書き込みなど別の原因。1つのゾーンだけ跳ねるなら、スクリプトエンジンのGC(mem-script-gc)かそのゾーンの負荷
確認手段
インフラのツールで確認(ゲームコード不要)
もっと詳しく
JavaのG1は1回あたりの停止目標がデフォルトで200msなので、20ティックのサーバーでは4ティック分に相当します。コンテナにCPUを2個未満、またはメモリを約1.8GB未満しか割り当てないと、JDK 26以前のJavaは単一スレッドで回収するSerial GCをデフォルトのGCとして選び、停止がはるかに長くなります。C#(.NET)のサーバーは通常サーバーGCとバックグラウンドGCを有効にしますが、新しいオブジェクトを入れる第0・第1世代(Gen0・1)の回収と、コンパクション(圧縮)を伴うFull GCは依然として全スレッドを止めます。Goは停止が通常1ms未満ですが、アロケーションが多いとメモリを要求した側がGC作業を分担しなければならず、ティックが遅くなります。どの方式でも、アロケーションが回収より速ければ最終的にゲームスレッドが止まります。G1はFull GCに移行し、ZGCはメモリを要求したスレッドを回収が終わるまで止めます。
実際の事例
Riot Games 2021: League of Legends EUWの5時間障害:補助的なDB一つでサーバー全体が停止

出典

  1. Garbage-First (G1) Garbage Collector Oracle
    G1の停止目標のデフォルト値は200ms(MaxGCPauseMillis)、回収中にメモリが尽きるとヒープ全体を止めてコンパクションするFull GCに移行
  2. JEP 523: Make G1 the Default Garbage Collector in All Environments OpenJDK
    これまではCPUが1個、またはメモリが1792MB未満だとSerial GCがデフォルトで選ばれ、JDK 27からはどの環境でもG1がデフォルト
  3. JEP 439: Generational ZGC OpenJDK
    ZGCの停止は1ms以下でヒープサイズに依存しない、G1の停止は数ms〜数秒。アロケーションが回収より速いとアロケーションストール(allocation stall)のおそれ
  4. JEP 189: Shenandoah: A Low-Pause-Time Garbage Collector (Experimental) OpenJDK
    Shenandoahの停止時間はヒープが200MBでも200GBでもほぼ同じ
  5. Background garbage collection .NET
    バックグラウンドGCは第2世代の回収にのみ適用され、第0・第1世代の回収(フォアグラウンドGC)はマネージドスレッドをすべて止める
  6. A Guide to the Go Garbage Collector Go
    Go GCは大部分が並行して動き、短い全停止があるだけ。アロケーションが多いとゴルーチンがGC作業を肩代わりし(assist)、遅延が発生
  7. JEP 271: Unified GC Logging OpenJDK
    JDK 9からGCログを統合ロギング(-Xlog)で再実装、-Xlog:gcは以前の-XX:+PrintGCと同様にGCごとに1行
  8. The java Command Oracle
    旧GCログオプションを-Xlogに置き換える対応表:-XX:+PrintGCDetailsは-Xlog:gc*
  9. dotnet-counters diagnostic tool .NET
    .NET 9以降はSystem.Runtimeのメーター(dotnet.gc.pause.timeなど)、.NET 8以前は旧EventCounter(% Time in GC since last GCなど)で表示
  10. runtime package Go
    GODEBUG=gctrace=1:GCごとに1行、フェーズごとの経過時間(ウォールクロック)、GC開始時・終了時のヒープサイズと目標ヒープ

あわせて読みたい原因

同じ層:L10 メモリ

同じ症状(フリーズ)を起こすほかの層の原因

図と実験のあるメインページでこのカードを見る