全部学科
NodeJS全栈
nodejs
Python全栈
python
小程序首页
📅 2026-05-14 8 分钟 ✍️ juanwangdev

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字段

字段含义
NumGCGC次数
PauseTotalNs总暂停时间
PauseNs各次暂停时间
NextGC下次GC阈值
GCCPUFractionGC占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.19GOMEMLIMIT内存限制

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目标是低延迟而非零延迟

📝 发现内容有误?点击此处直接编辑

← 上一篇 Go time包
下一篇 → Go Goroutine与GMP模型
想查看更多题目和详细解析?
小程序提供完整的题库、模拟考试和详细解析
马上就来

长按或扫描二维码,立即体验

扫码体验小程序
马上就来
使用微信扫描二维码
立即体验完整题库