4.1. 垃圾回收 (GC)

4.1.1. Go GC 概述

Go 使用并发、三色标记、写屏障的垃圾回收器。

特性

描述

算法

三色标记清除

类型

并发、非分代

STW

极短(通常 < 1ms)

目标

低延迟

4.1.2. GC 触发条件

  1. 堆内存达到阈值:当堆大小达到上次 GC 后大小的一定比例(由 GOGC 控制)

  2. 定时触发:2 分钟强制执行一次 GC

  3. 手动触发:调用 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
    
  1. 白色:未被访问的对象,GC 结束后会被回收

  2. 灰色:已被访问但其引用的对象未全部访问

  3. 黑色:已被访问且其引用的对象已全部访问

4.1.4.1. 标记过程

  1. STW,启用写屏障

  2. 从根对象(栈、全局变量)开始标记

  3. 并发标记(与用户程序并行)

  4. STW,完成标记

  5. 并发清除

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

字段

含义

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 触发的目标堆大小

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)

解决

  • 减少指针数量

  • 减少全局变量

  • 使用更小的堆

4.1.10. 参考资源