遊戲 Lag 白皮書 › L10 記憶體
伺服器 GC 全面暫停 Stop-the-world GC pause
原因 ID mem-gc · 主要負責 遊戲開發團隊(伺服器開發) · 協同 基礎設施團隊(伺服器基礎設施)
在含圖解與實驗的完整版中開啟卡片 →
Java、C# 伺服器為了回收垃圾而暫停所有執行緒(stop-the-world)的期間,整個伺服器都會停住。
為什麼 heap 滿了,開始 GC → 於是 暫停所有遊戲執行緒進行回收(存活資料越多,暫停越久) → 畫面上 伺服器上的所有人同時定格,接著出現快轉
症狀 定格 , 快轉
因素 停滯
誰會遇到 整個伺服器
何時 固定週期, 開越久越嚴重
負責單位 主要負責 遊戲開發團隊(伺服器開發) · 協同 基礎設施團隊(伺服器基礎設施)
遊戲開發團隊要做的事 在啟動選項中明確指定暫停較短的 GC(ZGC、Shenandoah、調低暫停目標的 G1)、減少記憶體配置、調整 heap 大小。
基礎設施團隊要做的事 選用記憶體足以讓 heap 保有充裕空間的執行個體;容器至少配置 2 個 CPU 與約 1.8GB 記憶體(JDK 26 以下若低於此值,會預設選用 Serial GC);監控 GC 暫停時間。
數值參考 只回收新物件(Young 區)的 Minor GC 約需數~數十 ms。回收整個 heap(存活資料達數 GB)的 Full GC 有時會超過 1 秒。ZGC 的暫停幾乎與 heap 大小無關,不到 1ms;Shenandoah 的暫停也不會隨 heap 大小成比例增加,因此同樣很短。
圖表上 固定週期飆高 · 伺服器 tick 時間、GC 暫停時間
查看位置 開啟 GC log,把暫停的時間點與長度疊在伺服器 tick 時間圖表上。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 輸出的一行 符合的跡象 tick 飆高的時間點與 GC 暫停重疊,暫停長度與飆高長度相近。伺服器所有 zone、頻道在同一瞬間飆高 不符合的跡象 GC log 沒有長暫停、tick 仍飆高時,是鎖、同步呼叫、磁碟寫入等其他原因。只有一個 zone 飆高時,是腳本引擎的 GC(mem-script-gc)或該 zone 的負載 確認方式 用基礎設施工具確認(不需要遊戲程式碼)
深入了解 Java 的 G1 預設單次暫停目標為 200ms,在 20 tick 伺服器上相當於 4 個 tick。容器若只分到不到 2 個 CPU 或不到約 1.8GB 記憶體,JDK 26 以下的 Java 會預設選用以單一執行緒回收的 Serial GC,暫停時間會長得多。C#(.NET)伺服器通常會開啟伺服器 GC 與背景 GC,但存放新物件的第 0、1 代(Gen0、1)回收,以及伴隨壓縮的 Full GC,仍會暫停所有執行緒。Go 的暫停通常不到 1ms,但配置量大時,要求記憶體的一方必須分擔 GC 工作,tick 因此變慢。不論哪種方式,只要配置速度快過回收速度,遊戲執行緒最後都會停住。G1 會轉為 Full GC,ZGC 則會讓要求記憶體的執行緒停住,直到回收結束。
實際案例 Riot Games 2021: League of Legends EUW 5 小時事故:一個附屬 DB 讓整個伺服器停擺
出處 Garbage-First (G1) Garbage Collector Oracle G1 暫停目標預設值 200ms(MaxGCPauseMillis);回收期間記憶體耗盡時,會轉為暫停並壓縮整個 heap 的 Full GC JEP 523: Make G1 the Default Garbage Collector in All Environments OpenJDK 過去在只有 1 個 CPU 或記憶體不到 1792MB 時會預設選用 Serial GC;從 JDK 27 起,所有環境都預設使用 G1 JEP 439: Generational ZGC OpenJDK ZGC 暫停在 1ms 以下且與 heap 大小無關,G1 暫停為數 ms~數秒。配置快過回收時,有發生配置暫停(allocation stall)的風險 JEP 189: Shenandoah: A Low-Pause-Time Garbage Collector (Experimental) OpenJDK 不論 heap 是 200MB 還是 200GB,Shenandoah 的暫停時間都差不多 Background garbage collection .NET 背景 GC 只適用於第 2 代回收;第 0、1 代回收(前景 GC)會暫停所有受控執行緒 A Guide to the Go Garbage Collector Go Go GC 大多與程式同時執行,只有短暫的全面暫停;配置量大時,goroutine 須分擔 GC 工作(assist),因而產生延遲 JEP 271: Unified GC Logging OpenJDK 從 JDK 9 起以統一 logging(-Xlog)重新實作 GC log;-Xlog:gc 與舊的 -XX:+PrintGC 一樣,每次 GC 輸出一行 The java Command Oracle 將舊 GC log 選項換成 -Xlog 的對照表:-XX:+PrintGCDetails 對應 -Xlog:gc* dotnet-counters diagnostic tool .NET .NET 9 以後以 System.Runtime meter(dotnet.gc.pause.time 等)顯示,.NET 8 以下以舊的 EventCounter(% Time in GC since last GC 等)顯示 runtime package Go GODEBUG=gctrace=1:每次 GC 輸出一行,包含各階段的 wall clock 時間,以及 GC 開始、結束時的 heap 大小與目標 heap
相關原因
同一層:L10 記憶體
同一症狀(定格)在其他層的原因
查看含圖解與實驗的完整版卡片