# 垃圾回收 (GC) ```{contents} 目录 :depth: 3 ``` ## Go GC 概述 Go 使用**并发、三色标记、写屏障**的垃圾回收器。 | 特性 | 描述 | |------|------| | 算法 | 三色标记清除 | | 类型 | 并发、非分代 | | STW | 极短(通常 < 1ms) | | 目标 | 低延迟 | ## GC 触发条件 1. **堆内存达到阈值**:当堆大小达到上次 GC 后大小的一定比例(由 GOGC 控制) 2. **定时触发**:2 分钟强制执行一次 GC 3. **手动触发**:调用 `runtime.GC()` ## GOGC 环境变量 ```bash # 默认值 100:当堆增长 100% 时触发 GC GOGC=100 # 更激进的 GC(更低内存,更高 CPU) GOGC=50 # 更宽松的 GC(更高内存,更低 CPU) GOGC=200 # 禁用 GC(危险!) GOGC=off ``` ### 运行时设置 ```go import "runtime/debug" func main() { // 设置 GOGC debug.SetGCPercent(50) // 设置内存限制(Go 1.19+) debug.SetMemoryLimit(1 << 30) // 1GB } ``` ## 三色标记算法 ```{mermaid} graph LR subgraph "三色标记" W[白色
未访问] --> G[灰色
待处理] G --> B[黑色
已访问] end ``` 1. **白色**:未被访问的对象,GC 结束后会被回收 2. **灰色**:已被访问但其引用的对象未全部访问 3. **黑色**:已被访问且其引用的对象已全部访问 ### 标记过程 1. STW,启用写屏障 2. 从根对象(栈、全局变量)开始标记 3. 并发标记(与用户程序并行) 4. STW,完成标记 5. 并发清除 ## GC 监控 ### 查看 GC 日志 ```bash GODEBUG=gctrace=1 ./myprogram ``` 输出示例: ``` gc 1 @0.012s 2%: 0.026+0.44+0.003 ms clock, 0.10+0.32/0.27/0+0.012 ms cpu, 4->4->0 MB, 5 MB goal, 4 P ``` | 字段 | 含义 | |------|------| | `gc 1` | 第 1 次 GC | | `@0.012s` | 程序启动后 0.012 秒 | | `2%` | GC 占用 CPU 时间比例 | | `0.026+0.44+0.003 ms` | STW 时间 + 并发标记时间 + STW 时间 | | `4->4->0 MB` | GC 前堆大小 -> GC 时堆大小 -> GC 后存活大小 | | `5 MB goal` | 下次 GC 触发的目标堆大小 | ### 程序内监控 ```go import "runtime" func printGCStats() { var stats runtime.MemStats runtime.ReadMemStats(&stats) fmt.Printf("Alloc = %v MB\n", stats.Alloc/1024/1024) fmt.Printf("TotalAlloc = %v MB\n", stats.TotalAlloc/1024/1024) fmt.Printf("Sys = %v MB\n", stats.Sys/1024/1024) fmt.Printf("NumGC = %v\n", stats.NumGC) fmt.Printf("PauseTotalNs = %v ms\n", stats.PauseTotalNs/1e6) } ``` ## GC 调优 ### 1. 减少分配 ```go // ❌ 频繁分配 func process(items []Item) { for _, item := range items { result := &Result{} // 每次循环都分配 result.Process(item) } } // ✅ 复用对象 func process(items []Item) { result := &Result{} for _, item := range items { result.Reset() result.Process(item) } } ``` ### 2. 使用 sync.Pool ```go var resultPool = sync.Pool{ New: func() interface{} { return &Result{} }, } func process(item Item) { result := resultPool.Get().(*Result) defer resultPool.Put(result) result.Reset() result.Process(item) } ``` ### 3. 预分配内存 ```go // ❌ 动态增长 var data []byte for i := 0; i < 1000; i++ { data = append(data, byte(i)) } // ✅ 预分配 data := make([]byte, 0, 1000) for i := 0; i < 1000; i++ { data = append(data, byte(i)) } ``` ### 4. 使用值类型 ```go // ❌ 指针增加 GC 扫描压力 type Item struct { Name *string Value *int } // ✅ 值类型 type Item struct { Name string Value int } ``` ### 5. 设置内存限制(Go 1.19+) ```go import "runtime/debug" func init() { // 设置软内存限制 debug.SetMemoryLimit(512 << 20) // 512MB } ``` ## Go 1.19+ GOMEMLIMIT Go 1.19 引入了 `GOMEMLIMIT`,提供更精确的内存控制。 ```bash # 设置内存限制 GOMEMLIMIT=512MiB ./myprogram # 单位支持: B, KiB, MiB, GiB, TiB ``` ### GOMEMLIMIT vs GOGC | 特性 | GOGC | GOMEMLIMIT | |------|------|------------| | 控制方式 | 相对增长率 | 绝对内存限制 | | 适用场景 | 通用 | 容器环境 | | OOM 风险 | 可能 OOM | 更可控 | ## GC Pacer Go 1.18+ 改进了 GC Pacer 算法,提供更平滑的 GC 行为。 ```go // 查看 Pacer 状态 GODEBUG=gcpacertrace=1 ./myprogram ``` ## 常见问题 ### 问题 1:GC 频繁 **症状**:GC 日志显示频繁 GC,CPU 使用高 **解决**: - 增大 GOGC - 使用 sync.Pool - 减少分配 ### 问题 2:大堆内存 **症状**:内存持续增长 **解决**: - 检查内存泄漏 - 使用 GOMEMLIMIT - 降低 GOGC ### 问题 3:STW 时间长 **症状**:GC pause 时间长(> 10ms) **解决**: - 减少指针数量 - 减少全局变量 - 使用更小的堆 ## 参考资源 - [Go GC Guide](https://tip.golang.org/doc/gc-guide) - [Getting to Go: The Journey of Go's Garbage Collector](https://go.dev/blog/ismmkeynote) - [Go 1.19 Memory Limit](https://go.dev/doc/go1.19#runtime)