跳到主要内容

GC 调优

问题

如何调整 Go 的 GC 参数?GOGC 和 GOMEMLIMIT 怎么用?

答案

GOGC——控制 GC 频率

GOGC 控制 GC 触发阈值,默认值 100

含义:当堆内存从上次 GC 后增长了 GOGC% 时,触发下一次 GC。

下次 GC 目标堆大小 = 上次 GC 后存活的堆大小 × (1 + GOGC/100)
GOGC 值含义效果
100(默认)堆增长 100% 触发 GCCPU / 内存平衡
200堆增长 200% 触发 GCGC 更少,省 CPU,费内存
50堆增长 50% 触发 GCGC 更频繁,省内存,费 CPU
off关闭自动 GC仅测试用
# 设置环境变量
GOGC=200 ./myapp

# 或代码中设置
import "runtime/debug"
debug.SetGCPercent(200)
什么时候调 GOGC
  • 内存充裕但 CPU 被 GC 占用太多:调高 GOGC(如 200 ~ 400)
  • 内存紧张:调低 GOGC(如 50)
  • 延迟敏感:调高减少 GC 次数,或用 GOMEMLIMIT

GOMEMLIMIT(Go 1.19+)——内存上限

GOMEMLIMIT 设置 Go 运行时可使用的软内存上限

GOMEMLIMIT=1GiB ./myapp  # 限制为 1GB
GOMEMLIMIT=512MiB ./myapp

工作机制:

  • 当堆内存接近限制时,GC 会更积极地回收
  • 软限制——极端情况下可以超过(避免 OOM 循环)

GOGC + GOMEMLIMIT 配合使用

# 推荐配置:关闭 GOGC,只用 GOMEMLIMIT
GOGC=off GOMEMLIMIT=1GiB ./myapp

这种配置下:

  • GC 不再按堆增长比例触发
  • 而是在堆接近 1GB 时触发 GC
  • 可以最大化利用可用内存,减少不必要的 GC

GC 日志分析

GODEBUG=gctrace=1 ./myapp

输出格式:

gc 5 @0.231s 3%: 0.015+1.234+0.021 ms clock, 0.12+0.45/1.1/0+0.17 ms cpu, 8->9->4 MB, 9 MB goal, 0 MB stacks, 0 MB globals, 8 P
字段含义
gc 5第 5 次 GC
@0.231s程序启动 0.231 秒后
3%GC 占总 CPU 时间 3%
0.015+1.234+0.021 ms clockSTW1 + 并发标记 + STW2 的墙钟时间
8->9->4 MBGC 开始堆大小 → 标记结束堆大小 → 存活堆大小
9 MB goal下次 GC 目标堆大小
8 P使用 8 个处理器

实际调优策略

容器环境推荐配置:

# 容器内存限制 2GB
GOGC=off GOMEMLIMIT=1400MiB ./myapp
# 预留 ~600MB 给非堆内存(栈、goroutine、OS 等)

常见面试问题

Q1: GOGC 默认值 100 意味着什么?

答案

意味着当堆大小从上次 GC 后增长了 100%(翻倍)时触发下一次 GC。例如上次 GC 后存活 50MB,堆增长到 100MB 时触发 GC。

Q2: GOMEMLIMIT 和 GOGC 冲突怎么办?

答案

两者同时存在时,先满足的条件触发 GC

  • 堆增长到 GOGC 触发阈值 → GC
  • 堆接近 GOMEMLIMIT → GC

推荐在容器环境中用 GOGC=off + GOMEMLIMIT,让内存利用率最大化。

Q3: 如何判断 GC 是否是性能瓶颈?

答案

# 1. 看 GC CPU 占比
GODEBUG=gctrace=1 ./myapp
# 如果 GC 占 CPU > 5~10%,需要关注

# 2. pprof 看 GC 停顿
go tool pprof http://localhost:6060/debug/pprof/profile

# 3. runtime.ReadMemStats
var m runtime.MemStats
runtime.ReadMemStats(&m)
fmt.Println("GC 次数:", m.NumGC)
fmt.Println("GC 总暂停:", m.PauseTotalNs)

相关链接