4.1. 垃圾回收 (GC)
4.1.1. Go GC 概述
Go 使用并发、三色标记、写屏障的垃圾回收器。
特性 |
描述 |
|---|---|
算法 |
三色标记清除 |
类型 |
并发、非分代 |
STW |
极短(通常 < 1ms) |
目标 |
低延迟 |
4.1.2. GC 触发条件
堆内存达到阈值:当堆大小达到上次 GC 后大小的一定比例(由 GOGC 控制)
定时触发:2 分钟强制执行一次 GC
手动触发:调用
runtime.GC()
4.1.3. GOGC 环境变量
# 默认值 100:当堆增长 100% 时触发 GC
GOGC=100
# 更激进的 GC(更低内存,更高 CPU)
GOGC=50
# 更宽松的 GC(更高内存,更低 CPU)
GOGC=200
# 禁用 GC(危险!)
GOGC=off
4.1.3.1. 运行时设置
import "runtime/debug"
func main() {
// 设置 GOGC
debug.SetGCPercent(50)
// 设置内存限制(Go 1.19+)
debug.SetMemoryLimit(1 << 30) // 1GB
}
4.1.4. 三色标记算法
graph LR
subgraph "三色标记"
W[白色<br/>未访问] --> G[灰色<br/>待处理]
G --> B[黑色<br/>已访问]
end
白色:未被访问的对象,GC 结束后会被回收
灰色:已被访问但其引用的对象未全部访问
黑色:已被访问且其引用的对象已全部访问
4.1.4.1. 标记过程
STW,启用写屏障
从根对象(栈、全局变量)开始标记
并发标记(与用户程序并行)
STW,完成标记
并发清除
4.1.5. GC 监控
4.1.5.1. 查看 GC 日志
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
字段 |
含义 |
|---|---|
|
第 1 次 GC |
|
程序启动后 0.012 秒 |
|
GC 占用 CPU 时间比例 |
|
STW 时间 + 并发标记时间 + STW 时间 |
|
GC 前堆大小 -> GC 时堆大小 -> GC 后存活大小 |
|
下次 GC 触发的目标堆大小 |
4.1.5.2. 程序内监控
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)
}
4.1.6. GC 调优
4.1.6.1. 1. 减少分配
// ❌ 频繁分配
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)
}
}
4.1.6.2. 2. 使用 sync.Pool
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)
}
4.1.6.3. 3. 预分配内存
// ❌ 动态增长
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.1.6.4. 4. 使用值类型
// ❌ 指针增加 GC 扫描压力
type Item struct {
Name *string
Value *int
}
// ✅ 值类型
type Item struct {
Name string
Value int
}
4.1.6.5. 5. 设置内存限制(Go 1.19+)
import "runtime/debug"
func init() {
// 设置软内存限制
debug.SetMemoryLimit(512 << 20) // 512MB
}
4.1.7. Go 1.19+ GOMEMLIMIT
Go 1.19 引入了 GOMEMLIMIT,提供更精确的内存控制。
# 设置内存限制
GOMEMLIMIT=512MiB ./myprogram
# 单位支持: B, KiB, MiB, GiB, TiB
4.1.7.1. GOMEMLIMIT vs GOGC
特性 |
GOGC |
GOMEMLIMIT |
|---|---|---|
控制方式 |
相对增长率 |
绝对内存限制 |
适用场景 |
通用 |
容器环境 |
OOM 风险 |
可能 OOM |
更可控 |
4.1.8. GC Pacer
Go 1.18+ 改进了 GC Pacer 算法,提供更平滑的 GC 行为。
// 查看 Pacer 状态
GODEBUG=gcpacertrace=1 ./myprogram
4.1.9. 常见问题
4.1.9.1. 问题 1:GC 频繁
症状:GC 日志显示频繁 GC,CPU 使用高
解决:
增大 GOGC
使用 sync.Pool
减少分配
4.1.9.2. 问题 2:大堆内存
症状:内存持续增长
解决:
检查内存泄漏
使用 GOMEMLIMIT
降低 GOGC
4.1.9.3. 问题 3:STW 时间长
症状:GC pause 时间长(> 10ms)
解决:
减少指针数量
减少全局变量
使用更小的堆