游戏卡顿白皮书 › L10 内存
GC 颠簸(堆余量不足) GC thrashing (heap nearly full)
原因 ID mem-gc-thrash · 主责 研发团队·服务器开发 · 配合 运维团队·系统运维
在含图示和实验的完整版中打开此卡片 →
存活数据接近堆上限时,GC 即使运行也几乎回收不到东西,于是不停地反复执行。
起因 活动人数增加或内存泄漏,让存活数据逼近堆上限 → 结果 GC 只能回收一点点,紧接着又是 Full GC,CPU 大部分时间都花在 GC 上 → 画面表现 全服几分钟内反复出现慢动作、卡住,最后因内存不足而终止
- 症状
- 慢动作, 卡住, 掉线
- 因素
- 停顿
- 谁会遇到
- 全服
- 何时出现
- 晚高峰, 人多的时候, 开得越久越严重
- 负责方
- 主责 研发团队·服务器开发 · 配合 运维团队·系统运维
- 研发团队要做的事
- 堆大小要比高峰时的存活数据留足余量(通常 2 倍以上),减少长期引用的数据和泄漏。
- 运维团队要做的事
- 为 GC 时间占比设置告警,不要硬撑、尽早重启,选用内存充裕的实例以便调大堆。
- 数值参考
- GC 占用超过 10% 的运行时间,一般就视为危险信号。Java 的部分 GC 如果把 98% 的时间花在 GC 上却几乎回收不到内存,就会抛出内存不足错误。
- 监控图上
- 触顶后走平 · GC 后的堆、GC 时间占比
- 查看位置
- 看 GC 后剩余的堆离最大堆有多近,以及花在 GC 上的时间占比。Java 看 -Xlog:gc 行里的“GC 后(堆大小)”和 Pause Full 行出现的频率,.NET 看 dotnet-counters(.NET 8 及以下为 % Time in GC since last GC,.NET 9 起看 dotnet.gc.pause.time 的增量),Go 看 GODEBUG=gctrace=1 各行之间的间隔
- 确认依据
- GC 刚结束堆仍接近最大值,Full GC 接连执行,GC 时间占比明显高于平时(通常超过 10%)。这段时间全服 tick 一起变慢
- 排除依据
- GC 后堆还有余量、只是停顿长,属于 GC 类型或配置问题,看“服务器 GC 全局停顿”(mem-gc)
- 确认手段
- 运维工具即可确认(无需游戏代码)
出处
- The Parallel Collector Oracle
Parallel GC 如果超过 98% 的总时间都在做 GC,却只回收不到 2% 的堆,就抛出 OutOfMemoryError - Garbage-First Garbage Collector Tuning Oracle
G1 默认(GCTimeRatio=12)按照 GC 时间不超过总时间约 8% 的目标确定堆大小;堆占用过高引发的 Full GC,可在日志中通过 Pause Full (G1 Compaction Pause) 找到 - A Guide to the Go Garbage Collector Go
默认 GOGC=100 时目标堆约为存活堆的 2 倍;贴近内存上限时 GC 会不停运行,形成颠簸(thrashing);用 GODEBUG=gctrace=1 输出 GC 跟踪信息 - Garbage Collector Implementation Oracle
-Xlog:gc 行包含 GC 类型(Pause Young、Pause Full)、“GC 前占用->GC 后占用(堆大小)”和停顿时间 - dotnet-counters diagnostic tool .NET
.NET 9 起显示为 dotnet.gc.pause.time,.NET 8 及以下显示为 % Time in GC since last GC
相关原因
同一层:L10 内存
其他层中同样导致“慢动作”的原因
查看含图示和实验的原卡片