运行时与调试
包总览
| 包 | 核心功能 | 用途 |
|---|
runtime | GC/GOMAXPROCS/goroutine | 运行时控制 |
runtime/debug | Stack/GC/BuildInfo | 调试信息 |
runtime/pprof | CPU/内存/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(默认) | 中 | 中 | 中 | 通用 |
| 200 | 低 | 高 | 低 | CPU 敏感 |
| 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 profile | goroutine 泄漏 | 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