GC 调优
问题
如何调整 Go 的 GC 参数?GOGC 和 GOMEMLIMIT 怎么用?
答案
GOGC——控制 GC 频率
GOGC 控制 GC 触发阈值,默认值 100。
含义:当堆内存从上次 GC 后增长了 GOGC% 时,触发下一次 GC。
下次 GC 目标堆大小 = 上次 GC 后存活的堆大小 × (1 + GOGC/100)
| GOGC 值 | 含义 | 效果 |
|---|---|---|
100(默认) | 堆增长 100% 触发 GC | CPU / 内存平衡 |
200 | 堆增长 200% 触发 GC | GC 更少,省 CPU,费内存 |
50 | 堆增长 50% 触发 GC | GC 更频繁,省内存,费 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 clock | STW1 + 并发标记 + STW2 的墙钟时间 |
8->9->4 MB | GC 开始堆大小 → 标记结束堆大小 → 存活堆大小 |
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)