应使用 runtime.readmemstats(&ms) 获取精确堆内存快照,ms.alloc 表示当前未回收的活跃堆字节数,需除以1024转kb;真实物理内存须读 /proc/self/status 中 vmrss 字段。

怎么用 runtime.ReadMemStats 看真实堆内存
runtime.ReadMemStats 是唯一能拿到 Go 运行时精确堆状态的入口,它返回的 Alloc 字段才是你该盯住的“当前活跃内存”,不是 ps aux 里的 RSS,也不是 top 里那个抖动的 VIRT。
- 必须传非 nil 的
*runtime.MemStats指针,否则 panic -
Alloc是已分配且未被 GC 回收的字节数,单位字节;除以 1024 才是 KB -
Sys表示向操作系统申请的总虚拟内存,包含堆、栈、mmap 分配等,不能代表实际物理占用 - 调用开销在微秒级,可每 2–5 秒安全采集一次,但别塞进高频 HTTP handler 里
示例:
var ms runtime.MemStats
runtime.ReadMemStats(&ms)
fmt.Printf("active heap: %d KB\n", ms.Alloc/1024)
/proc/self/status 里找 VmRSS 才是真实物理内存
Go 应用吃多少物理内存,runtime.ReadMemStats 说不清楚——它不统计 cgo 分配、大块 mmap、线程栈或 runtime 自身元数据。/proc/self/status 的 VmRSS 字段才是 Linux 内核给出的真实物理页占用(单位 KB),和 ps -o rss= -p $PID 一致。
- 读取路径固定为
/proc/self/status,无需拼 PID - 需按行扫描,匹配以
VmRSS:开头的行,提取后一个数字字段 - 注意:该文件可能因权限或容器环境不可读(如 rootless 容器),要加 error fallback
- 不要每秒都读——/proc 是虚拟文件系统,频繁读会增加 syscall 开销,建议与
ReadMemStats同步采样
关键片段:
data, _ := os.ReadFile("/proc/self/status")
for _, line := range strings.Split(string(data), "\n") {
if strings.HasPrefix(line, "VmRSS:") {
parts := strings.Fields(line)
if len(parts) > 1 {
rss, _ := strconv.ParseInt(parts[1], 10, 64)
fmt.Printf("os_rss_kb: %d\n", rss)
}
}
}
goroutine 数暴涨?先看 runtime.NumGoroutine() + pprof/goroutine?debug=2
runtime.NumGoroutine() 返回值跳变不是误报,是泄漏信号。它原子读取调度器状态,数值本身精确,但持续上涨说明有 goroutine 卡死在 channel receive、time.Sleep、syscall 或 context.Done() 上。
- 健康检查接口中调用它是安全的,但别在每毫秒请求里调用
- 若发现数值缓慢爬升,立刻 curl
http://localhost:6060/debug/pprof/goroutine?debug=2查完整栈 - 重点关注阻塞在
select {}、ch 、<code> 或 <code>net/http.(*conn).serve的 goroutine - HTTP handler 中忘记
resp.Body.Close()或没设context.WithTimeout是常见根源
别在 handler 里实时读 proc 或调 ReadMemStats
所有监控端点(比如 GET /metrics)必须返回预计算缓存值,而不是每次请求都触发 ReadMemStats 或读 /proc/self/status。前者虽快但累积仍影响吞吐,后者涉及 syscall 和文件 I/O,在高并发下会成为瓶颈。
- 启动时起一个后台 goroutine,用
time.Ticker每 3 秒采集一次并写入全局 struct - handler 只做 atomic load 或 mutex-protected copy,零额外开销
- 如果用了
github.com/shirou/gopsutil/v3,它的process.MemoryInfo()也得同样缓存,别裸调 - 尤其注意:Prometheus 抓取频率常是 15s,你的采集频率设成 3s 就够,再密纯属浪费
真正容易被忽略的是缓存一致性——多个采集项(CPU、内存、goroutine)如果不同步采样,指标之间会出现逻辑矛盾,比如内存已涨但 goroutine 数没变。统一用单次 tick 触发全部采集,才能保证观测可信。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











