Go垃圾回收机制
Go采用并发三色标记清除算法,实现低延迟垃圾回收。
GC算法概述
三色标记法
Go
白色:未扫描对象(可能被回收)
灰色:已扫描但引用未扫描(待处理)
黑色:已扫描且引用已扫描(存活)
标记清除流程
Go
1. 初始所有对象白色
2. 从根对象开始,标记为灰色
3. 扫描灰色对象:
- 对象引用标记灰色
- 自己标记黑色
4. 重复直到灰色为空
5. 清除白色对象
并发GC实现
STW阶段
Go
GC开始 → 短暂STW(开启写屏障)
↓ 并发标记
标记结束 → 短暂STW(关闭写屏障)
↓ 并发清除
Go 1.5+ STW时间通常在100微秒级别。
GC阶段表
| 阶段 | STW | 操作 |
|---|---|---|
| GCMarkPrepare | 是 | 开启写屏障 |
| GCMark | 否 | 并发标记 |
| GCMarkTermination | 是 | 关闭写屏障 |
| GCMarkSweep | 否 | 并发清除 |
写屏障
混合写屏障
Go
// Go 1.8+使用混合写屏障
// 插入写屏障 + 删除写屏障
// 插入屏障:记录新引用
func writebarrier(slot, new) {
shade(new) // 新对象变灰
*slot = new
}
// 删除屏障:记录旧引用
func writebarrier(slot, new) {
shade(*slot) // 旧对象变灰
*slot = new
}
屏障作用
Go
并发标记期间:
用户修改对象引用
↓
写屏障记录变更
↓
防止对象丢失标记
↓
保证GC正确性
写屏障确保并发标记时不会漏标记存活对象。
GC触发时机
触发条件
Go
1. 内存阈值触发
heapSize > heapTrigger
trigger = 2 * heapGoal
2. 手动触发
runtime.GC()
3. 定时触发
每2分钟强制GC(如无其他触发)
GOGC参数
Go
// 默认GOGC=100
// 表示:heapTrigger = heapSize + heapSize*GOGC/100
// 堆增长100%触发GC
// 例如:当前100MB → 达到200MB触发
// 设置GOGC
runtime.SetFinalizer(obj, finalizer)
GOGC=200 ./app
GC工作流程
mark阶段
Go
func gcStart() {
// 1. STW开启写屏障
stopTheWorld()
enableWriteBarrier()
// 2. 并发标记
startTheWorld()
gcMark()
// 3. STW关闭写屏障
stopTheWorld()
disableWriteBarrier()
gcMarkTermination()
// 4. 并发清除
startTheWorld()
gcSweep()
}
mark工作
Go
func gcMark() {
// 标记所有根对象
scanRoots()
// 并发扫描灰色对象
for grey != nil {
obj := grey.pop()
scanObject(obj) // 扫描引用
obj.color = black
}
}
根对象
扫描起点
Bash
1. 全局变量
2. Goroutine栈
3. 寄存器
Bash
// 栈扫描
func scanstack(gp *g) {
// 扫描栈上的指针
// 标记引用对象为灰色
}
Sweep清除
惰性清除
Go
func gcSweep() {
// 不一次性清除全部
// 惰性清除:在需要时清除Span
// Span分配时检查是否需要清除
if span.sweepgen != sweepgen {
sweep(span)
}
}
惰性清除分散清除开销,避免一次性大量清除。
GC统计
runtime.GCStats
text
var stats debug.GCStats
runtime.SetGCStats(&stats)
fmt.Println(stats.NumGC) // GC次数
fmt.Println(stats.PauseTotal) // 总暂停时间
fmt.Println(stats.Pause) // 各次暂停时间
ReadMemStats
text
var m runtime.MemStats
runtime.ReadMemStats(&m)
fmt.Println("GC次数:", m.NumGC)
fmt.Println("GC暂停:", m.PauseTotalNs)
fmt.Println("堆大小:", m.HeapAlloc)
MemStats GC字段
| 字段 | 含义 |
|---|---|
| NumGC | GC次数 |
| PauseTotalNs | 总暂停时间 |
| PauseNs | 各次暂停时间 |
| NextGC | 下次GC阈值 |
| GCCPUFraction | GC占CPU时间比例 |
| EnableGC | 是否启用GC |
| DebugGC | 是否调试GC |
GC调优
减少GC压力
text
// 1. 减少堆分配
// 使用栈分配(小对象)
// 复用对象(sync.Pool)
var pool = sync.Pool{
New: func() interface{} {
return &Buffer{}
},
}
buf := pool.Get().(*Buffer)
// 使用buf
pool.Put(buf)
// 2. 预分配内存
data := make([]byte, 10000) // 预分配
// 3. 避免频繁分配
strings.Builder // 拼接字符串
GOGC调整
text
# 降低GC频率(减少GC开销)
GOGC=200 ./app
# 提高GC频率(减少内存占用)
GOGC=50 ./app
# 禁用GC(不推荐)
GOGC=off ./app
GC性能指标
目标
text
GC目标:heapGoal
heapGoal = heapSize + heapSize * GOGC / 100
GC占CPU比例目标:GOMEMLIMIT
默认:无限制
设置:GOMEMLIMIT=1GiB
Go 1.19+引入GOMEMLIMIT,可限制内存使用。
GC调试
GODEBUG选项
text
# 查看GC详情
GODEBUG=gctrace=1 ./app
# 输出示例
gc 1 @0.001s 0%: 0.018+0.12+0.015 ms clock, 0.14+0.069/0.12/0.076+0.12 ms cpu
gctrace输出解读
text
gc 1 @0.001s 0%:
0.018+0.12+0.015 ms clock
↓
STW开始 + 并发标记 + STW结束
GC触发计算
公式
text
trigger = heapSize + heapSize * GOGC / 100
例如:
heapSize = 100MB
GOGC = 100
trigger = 100 + 100 * 1 = 200MB
GC后heapSize = 100MB
下次trigger = 200MB
GC版本演进
| 版本 | 算法 | 特点 |
|---|---|---|
| Go 1.3 | 标记清除 | 全STW |
| Go 1.5 | 并发三色 | STW极短 |
| Go 1.8 | 混合屏障 | STW <100us |
| Go 1.19 | GOMEMLIMIT | 内存限制 |
GC监控
pprof分析
text
import _ "net/http/pprof"
// 查看GC情况
go tool pprof http://localhost:6060/debug/pprof/allocs
// 查看内存分配
go tool pprof http://localhost:6060/debug/pprof/heap
要点总结
- 三色标记:白色未扫描,灰色待处理,黑色存活
- 并发标记减少STW时间
- 写屏障保证并发标记正确性
- 混合写屏障结合插入和删除屏障
- GOGC默认100,堆增长触发GC
- 惰性清除分散清除开销
- sync.Pool减少分配压力
- GODEBUG=gctrace查看GC详情
- ReadMemStats获取GC统计
- GOMEMLIMIT限制内存使用
- GC目标是低延迟而非零延迟
📝 发现内容有误?点击此处直接编辑