文章

运行时与调试

运行时与调试

包总览

核心功能用途
runtimeGC/GOMAXPROCS/goroutine运行时控制
runtime/debugStack/GC/BuildInfo调试信息
runtime/pprofCPU/内存/goroutine profile性能分析
runtime/trace执行追踪时序分析
runtime/metrics运行时指标监控

runtime 包

常用函数

import "runtime"

// goroutine 数量
n := runtime.NumGoroutine()

// CPU 核数
n := runtime.NumCPU()

// GOMAXPROCS(逻辑处理器数)
n := runtime.GOMAXPROCS(0)     // 查询(不修改)
runtime.GOMAXPROCS(4)          // 设置为 4

// 主动让出 CPU(给其他 goroutine 机会)
runtime.Gosched()

// 退出当前 goroutine
runtime.Goexit()

// 内存统计
var m runtime.MemStats
runtime.ReadMemStats(&m)
m.Alloc      // 当前分配的字节数
m.TotalAlloc // 历史总分配字节数
m.Sys        // 从 OS 获取的总内存
m.NumGC      // GC 次数
m.GCCPUFraction // GC 占 CPU 时间比例

// 强制 GC(仅测试用,生产不要用)
runtime.GC()

// 获取调用栈
buf := make([]byte, 4096)
n := runtime.Stack(buf, false)  // false=当前goroutine, true=所有
fmt.Println(string(buf[:n]))

runtime.MemStats 详解

var m runtime.MemStats
runtime.ReadMemStats(&m)

// 内存使用
m.Alloc         // 已分配且未回收的内存(字节)
m.TotalAlloc    // 历史总分配量
m.Sys           // 从 OS 获取的总内存
m.HeapAlloc     // 堆上分配的内存
m.HeapInuse     // 堆上正在使用的内存
m.HeapIdle      // 堆上空闲的内存
m.HeapReleased  // 已归还给 OS 的内存

// GC 统计
m.NumGC         // GC 次数
m.PauseNs       // 上次 GC 暂停时间(纳秒)
m.PauseTotalNs  // GC 总暂停时间
m.GCCPUFraction // GC 占 CPU 时间的比例
m.NextGC        // 下次 GC 目标堆大小

// 格式化输出
fmt.Printf("Alloc: %s, Sys: %s, NumGC: %d\n",
    humanize.Bytes(m.Alloc),
    humanize.Bytes(m.Sys),
    m.NumGC)

GC 调优

// GOGC:控制 GC 触发频率(默认 100)
// GOGC=100 表示堆增长 100% 时触发 GC
// GOGC=200 触发更少(内存使用更多,CPU 更少)
// GOGC=50  触发更频繁(内存使用更少,CPU 更多)
// GOGC=off 关闭 GC(仅短生命周期程序用)

// Go 1.19+:内存限制(GOMEMLIMIT)
debug.SetMemoryLimit(1 << 30)  // 1GB 内存限制

// Go 1.18+:软内存限制
debug.SetGCPercent(-1)  // 关闭 GC(配合 SetMemoryLimit)
debug.SetGCPercent(100)  // 恢复默认
GOGC 值GC 频率内存使用CPU 占用适用
50内存敏感
100(默认)通用
200CPU 敏感
off无限0短任务

runtime/debug 包

import "runtime/debug"

// 获取调用栈
stack := debug.Stack()  // []byte
fmt.Println(string(stack))

// 获取构建信息(Go 1.18+)
info, ok := debug.ReadBuildInfo()
if ok {
    fmt.Println(info.Main.Path)     // 模块路径
    fmt.Println(info.Main.Version)  // 版本
    fmt.Println(info.GoVersion)     // Go 版本
    for _, dep := range info.Deps {
        fmt.Printf("  %s %s\n", dep.Path, dep.Version)
    }
}

// 设置 GC 参数
debug.SetGCPercent(50)
debug.SetMaxStack(1000000000)     // 最大栈空间(1GB)
debug.SetMaxThreads(10000)        // 最大线程数
debug.SetMemoryLimit(2 << 30)     // 2GB 内存限制

// 强制 GC 并等待完成
debug.FreeOSMemory()  // 将空闲内存归还给 OS

runtime/pprof 包

CPU Profile

import "runtime/pprof"

// 方式1:程序结束时生成
func main() {
    f, _ := os.Create("cpu.prof")
    defer f.Close()
    pprof.StartCPUProfile(f)
    defer pprof.StopCPUProfile()

    // 业务代码
    doWork()
}

// 方式2:通过 HTTP 端点(推荐生产环境)
import _ "net/http/pprof"

go func() {
    http.ListenAndServe("localhost:6060", nil)
}()

// 然后用 go tool pprof 采集:
// go tool pprof http://localhost:6060/debug/pprof/profile?seconds=30

内存 Profile

// 程序结束时生成
f, _ := os.Create("mem.prof")
defer f.Close()
runtime.GC()  // 先 GC,获取准确数据
pprof.WriteHeapProfile(f)

// 或通过 HTTP:
// go tool pprof http://localhost:6060/debug/pprof/heap

goroutine Profile

// 通过 HTTP 获取所有 goroutine 堆栈
// go tool pprof http://localhost:6060/debug/pprof/goroutine

// 程序中获取
p := pprof.Lookup("goroutine")
p.WriteTo(os.Stdout, 1)  // 1=文本格式, 2=详细格式

pprof 分析命令

# CPU 分析(交互式)
go tool pprof cpu.prof
(pprof) top              # 显示耗时最多的函数
(pprof) top10            # 前10
(pprof) list funcName    # 查看函数代码级耗时
(pprof) web              # 浏览器可视化(需要 graphviz)
(pprof) tree             # 树状视图

# 内存分析
go tool pprof -alloc_space mem.prof      # 分配量
go tool pprof -alloc_objects mem.prof    # 分配对象数
go tool pprof -inuse_space mem.prof      # 当前使用量
go tool pprof -inuse_objects mem.prof    # 当前使用对象数

# 火焰图
go tool pprof -http=:8080 cpu.prof      # 浏览器打开火焰图

# 对比两个 profile
go tool pprof -base baseline.prof new.prof

# HTTP 端点采集
go tool pprof http://localhost:6060/debug/pprof/profile?seconds=30   # CPU
go tool pprof http://localhost:6060/debug/pprof/heap                  # 内存
go tool pprof http://localhost:6060/debug/pprof/goroutine             # goroutine
go tool pprof http://localhost:6060/debug/pprof/block                 # 阻塞
go tool pprof http://localhost:6060/debug/pprof/mutex                 # 锁竞争
go tool pprof http://localhost:6060/debug/pprof/threadcreate          # 线程创建

pprof HTTP 端点安全

// ⚠️ 生产环境不要直接暴露 pprof 端口!
// 方式1:绑定到 localhost
go func() {
    http.ListenAndServe("127.0.0.1:6060", nil)
}()

// 方式2:使用独立 mux,不暴露到公网
pprofMux := http.NewServeMux()
pprofMux.HandleFunc("/debug/pprof/", pprof.Index)
pprofMux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
pprofMux.HandleFunc("/debug/pprof/profile", pprof.Profile)
pprofMux.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
pprofMux.HandleFunc("/debug/pprof/trace", pprof.Trace)

go func() {
    http.ListenAndServe("127.0.0.1:6060", pprofMux)
}()

runtime/trace 包

import "runtime/trace"

// 生成 trace 文件
f, _ := os.Create("trace.out")
defer f.Close()
trace.Start(f)
defer trace.Stop()

// 业务代码
doWork()

// 分析:
// go tool trace trace.out
// 会打开浏览器,显示:
// - goroutine 调度时序
// - 阻塞分析
// - GC 时序
// - 系统调用耗时

trace 区域标记

// 在代码中标记区域
ctx, task := trace.NewTask(context.Background(), "processRequest")
defer task.End()

trace.WithRegion(ctx, "dbQuery", func() {
    // DB 查询
})

trace.WithRegion(ctx, "jsonMarshal", func() {
    // JSON 序列化
})

runtime/metrics 包(Go 1.16+)

import "runtime/metrics"

// 读取运行时指标
sample := []metrics.Sample{
    {Name: "/goroutines/count"},
    {Name: "/gc/heap/allocs:bytes"},
    {Name: "/gc/heap/goal:bytes"},
    {Name: "/memory/classes/heap/objects:bytes"},
    {Name: "/sched/goroutines:goroutines"},
    {Name: "/sched/threads:threads"},
}

metrics.Read(sample)

for _, s := range sample {
    switch s.Value.Kind() {
    case metrics.KindUint64:
        fmt.Printf("%s: %d\n", s.Name, s.Value.Uint64())
    case metrics.KindFloat64:
        fmt.Printf("%s: %f\n", s.Name, s.Value.Float64())
    }
}

性能分析检查清单

分析类型发现的问题命令
CPU profile热点函数go tool pprof cpu.prof
Heap profile内存泄漏go tool pprof -inuse_space heap.prof
Alloc profile分配过多go tool pprof -alloc_space heap.prof
Goroutine profilegoroutine 泄漏go tool pprof goroutine.prof
Block profile锁竞争/IO 阻塞go tool pprof block.prof
Mutex profile锁竞争go tool pprof mutex.prof
Trace调度问题go tool trace trace.out

启用 block/mutex profile

runtime.SetBlockProfileRate(1)     // 采样所有阻塞 >=1ns
runtime.SetMutexProfileFraction(1) // 采样所有锁竞争

常见性能问题排查

goroutine 泄漏

// 1. 查看 goroutine 数量
curl http://localhost:6060/debug/pprof/goroutine?debug=1

// 2. 如果数量持续增长,分析堆栈
go tool pprof http://localhost:6060/debug/pprof/goroutine?debug=2

// 3. 查看哪个函数创建了最多 goroutine
(pprof) top
(pprof) list <funcName>

内存泄漏

// 1. 查看当前内存使用
curl http://localhost:6060/debug/pprof/heap?debug=1

// 2. 分析 inuse_space(当前使用)
go tool pprof -inuse_space http://localhost:6060/debug/pprof/heap

// 3. 分析 alloc_space(历史分配)
go tool pprof -alloc_space http://localhost:6060/debug/pprof/heap

// 4. 对比两次采样(间隔一段时间)
go tool pprof -base heap1.prof heap2.prof

CPU 热点

// 1. 采集 30 秒 CPU profile
go tool pprof http://localhost:6060/debug/pprof/profile?seconds=30

// 2. 查看热点
(pprof) top10
(pprof) list <hotFunc>

// 3. 火焰图
go tool pprof -http=:8080 cpu.prof