GOGC 只看存活堆的倍数,不知道你的容器还剩多少内存。GOMEMLIMIT 给运行时一个软上限:接近上限时 GC 会更频繁,甚至在 GOGC=off 时也生效。这篇用同一个分配负载在五种设置下对比内存、GC 次数与吞吐,并验证它为什么是“软”的。
A 不设限制,GOGC=100(默认):
cycles=56 ops_per_s=109.6M peak_total_MB=609 maxrss_MB=605 gc_cpu_pct=28.5 limiter_last_cycle=0 goal_MB=562
(stderr)SetMemoryLimit(-1) 读到当前限制:9223372036854775807
B GOMEMLIMIT=300MiB,GOGC=100:
cycles=119 ops_per_s=44.7M peak_total_MB=299 maxrss_MB=293 gc_cpu_pct=51.9 limiter_last_cycle=0 goal_MB=268
C GOMEMLIMIT=300MiB,GOGC=off:
cycles=121 ops_per_s=45.5M peak_total_MB=299 maxrss_MB=293 gc_cpu_pct=53.4 limiter_last_cycle=0 goal_MB=268
D GOMEMLIMIT=240MiB,GOGC=off(只比存活堆多一点):
cycles=152 ops_per_s=11.2M peak_total_MB=245 maxrss_MB=241 gc_cpu_pct=65.2 limiter_last_cycle=0 goal_MB=213
E GOMEMLIMIT=150MiB,GOGC=100(低于存活堆):
cycles=129 ops_per_s=19.9M peak_total_MB=273 maxrss_MB=267 gc_cpu_pct=57.6 limiter_last_cycle=0 goal_MB=222
(stderr)SetMemoryLimit(-1) 读到当前限制:157286400文档(go doc runtime/debug.SetMemoryLimit)说限制针对 MemStats.Sys - HeapReleased(即 /memory/classes/total 减去 /memory/classes/heap/released),不包括二进制本身与 C 分配的内存。实验里限制 300MiB 把峰值压在 299 MB,GC 比不设限制多出一倍(119 轮对 56 轮),GC 的 CPU 占比从 28.5% 升到约 52%,吞吐掉到 41%。GOGC=off 加限制得到几乎相同的结果,说明限制自己就能驱动 GC。
GOMEMLIMIT=512MiB → SetMemoryLimit(-1) 读到当前限制:536870912 GOMEMLIMIT=1GiB → SetMemoryLimit(-1) 读到当前限制:1073741824 GOMEMLIMIT=1.5GiB → fatal error: malformed GOMEMLIMIT; see `go doc runtime/debug.SetMemoryLimit` GOMEMLIMIT=100MB → fatal error: malformed GOMEMLIMIT; see `go doc runtime/debug.SetMemoryLimit` GOMEMLIMIT=123456789 → SetMemoryLimit(-1) 读到当前限制:123456789 GOMEMLIMIT=off → SetMemoryLimit(-1) 读到当前限制:9223372036854775807 不设置 → SetMemoryLimit(-1) 读到当前限制:9223372036854775807(math.MaxInt64 = 9223372036854775807,即没有限制)
默认没有限制(math.MaxInt64)。GOMEMLIMIT 只接受整数加可选的 B、KiB、MiB、GiB、TiB 后缀,1.5GiB 或 100MB 会让程序启动失败。要用小数就换成更小的单位(1536MiB)。debug.SetMemoryLimit(-1) 可以读当前值。
A 不设限制,GOGC=100(默认):cycles=56 ops_per_s=109.6M peak_total_MB=609 maxrss_MB=605 gc_cpu_pct=28.5 limiter_last_cycle=0 goal_MB=562 E GOMEMLIMIT=150MiB,GOGC=100(低于存活堆):cycles=129 ops_per_s=19.9M peak_total_MB=273 maxrss_MB=267 gc_cpu_pct=57.6 limiter_last_cycle=0 goal_MB=222
限制低于存活堆时,程序不会被杀,也不保证内存不超:实验里 273 MB 超过了 157 MB 的限制,吞吐降到 18%,GC 轮数翻倍。文档的说法是这时 GC 可能“almost continuously”运行但应用仍能前进。实验里 /gc/limiter/last-enabled 读到 0,GC 占 CPU 57.6%,我没有观察到 GC 的 CPU 被限制在某个上限的证据(运行时是否有这样的限流,这次没有验证)。实践上的结论:限制应当设在存活堆之上留足余量的位置,而不是接近或低于它。