当前位置:首页 > 文章列表 > Golang > Go问答 > Go runtime.ReadMemStats 为什么不能直接代表 RSS

Go runtime.ReadMemStats 为什么不能直接代表 RSS

来源:17golang原创 2026-10-23 03:37:33 0浏览 收藏

Go 程序里调用 runtime.ReadMemStats 后,最容易出现一个误判:把 Sys 或 HeapAlloc 当成进程 RSS。它们不能直接代表 RSS,因为统计对象不同。HeapAlloc 更接近当前仍被 Go 堆对象使用的空间,Sys 记录的是 Go runtime 从操作系统获得并用于堆、栈及内部结构的地址空间,而 RSS 是操作系统看到的、当前驻留在物理内存中的进程页。

排查 Go 内存时,堆对象和 GC 问题看 MemStats 或 pprof;进程是否逼近机器或容器内存上限看 RSS 或 cgroup 指标。两条线要在同一时间点采集、并列展示,不能用一个字段换算另一个指标。

MemStats 和 RSS 统计的不是同一层

ReadMemStats 返回的是 Go 内存分配器的快照,适合回答“堆对象有多少、runtime 保留了多少、GC 最近怎样”。RSS 则回答“这个进程当前有多少页面驻留在物理内存”。一个是运行时内部视角,一个是操作系统进程视角。

指标它主要说明什么不能直接推出什么
HeapAlloc当前已分配且仍属于 Go 堆的对象字节数不能推出整个进程 RSS
HeapSys为堆取得的虚拟地址空间,含尚未使用或已归还物理页的部分不能当作已占用物理内存
SysGo runtime 为堆、栈和内部结构取得的总量不能当作 OS 进程工作集
HeapReleased已经归还给操作系统、尚未重新取得的堆物理内存不能单独说明 RSS 会立即下降多少
RSS进程当前驻留的物理页面不能单独定位是哪段 Go 代码分配的
Go runtime MemStats 与操作系统 RSS 统计层级说明图
图1:MemStats 与 RSS 的统计层级说明图,不是截图或运行证据。

先用 ReadMemStats 看清 Go runtime 自己

下面的采集函数只回答运行时问题:活跃堆、堆保留量、已归还量、栈和总量。注意 Sys 的单位是字节,且它描述的是 runtime 的地址空间保留;不要把它改名为 rss 写进监控。

package main

import (
    "fmt"
    "runtime"
)

func printRuntimeMemory() {
    var m runtime.MemStats
    // 读取调用时刻的 Go 分配器统计,不把字段伪装成 RSS。
    runtime.ReadMemStats(&m)

    fmt.Printf("heap_alloc=%d MiB\\n", m.HeapAlloc/1024/1024)
    fmt.Printf("heap_sys=%d MiB\\n", m.HeapSys/1024/1024)
    fmt.Printf("heap_idle=%d MiB\\n", m.HeapIdle/1024/1024)
    fmt.Printf("heap_released=%d MiB\\n", m.HeapReleased/1024/1024)
    fmt.Printf("stack_sys=%d MiB\\n", m.StackSys/1024/1024)
    fmt.Printf("runtime_sys=%d MiB\\n", m.Sys/1024/1024)
}

这组字段适合看趋势。例如 HeapAlloc 在 GC 后仍持续上升,才值得沿着堆对象和引用链继续查;HeapIdle - HeapReleased 较大,常见解释是 runtime 暂时保留了可复用的空闲 span,并不自动等于泄漏。

再在同一时刻读取进程 RSS

Linux 上可以读取当前进程的 /proc/self/status,其中 VmRSS 是操作系统进程视角的驻留集。这个示例是 Linux 特定实现,生产代码应把它封装成平台适配层;macOS、Windows 和容器运行时的采集入口并不相同。

//go:build linux

package main

import (
    "bufio"
    "fmt"
    "os"
    "strconv"
    "strings"
)

func readLinuxRSS() (uint64, error) {
    f, err := os.Open("/proc/self/status")
    if err != nil {
        return 0, err
    }
    defer f.Close() // 读取完成后关闭 proc 文件描述符。

    scanner := bufio.NewScanner(f)
    for scanner.Scan() {
        fields := strings.Fields(scanner.Text())
        if len(fields) == 3 && fields[0] == "VmRSS:" && fields[2] == "kB" {
            kb, err := strconv.ParseUint(fields[1], 10, 64)
            if err != nil {
                return 0, err
            }
            return kb * 1024, nil // /proc 的 VmRSS 单位是 kB。
        }
    }
    if err := scanner.Err(); err != nil {
        return 0, err
    }
    return 0, fmt.Errorf("VmRSS not found")
}

把 printRuntimeMemory 和 readLinuxRSS 放在同一次采样里,监控上至少保留 heap_alloc、runtime_sys、stack_sys、heap_reclaimable 和 rss 五条曲线。图中的数值关系是解释用的静态结构,不代表某次真实运行结果。

Go HeapAlloc、空闲保留、StackSys 与 RSS 差异的诊断关系图
图2:Go 内存差异诊断关系图,展示排查方向而非真实采样结果。

用指标组合判断差异来源

不要看到 RSS 大于 HeapAlloc 就直接下结论。可以按组合关系缩小范围:

  • HeapAlloc 与 RSS 一起持续上涨:优先用 heap profile、对象数量和引用链检查 Go 堆是否有长期存活对象。
  • HeapAlloc 回落,但 RSS 仍高:对照 HeapIdle - HeapReleased;runtime 可能保留可复用页面,也要看回收节奏和内存上限。
  • StackSys 或 goroutine 数量上升:检查 goroutine 泄漏、阻塞调用和线程栈;这部分不应归因给普通堆对象。
  • RSS 明显高,Go runtime 指标解释不了:转向 cgo 分配、直接 mmap、共享库映射和平台层内存统计;runtime/metrics 也明确有些 cgo 或 syscall 直接映射不在 Go runtime 的内存类别内。

容器环境还要把进程 RSS 与 cgroup 的内存使用口径对齐。告警阈值如果针对容器限制,就读取对应 cgroup 指标;如果要定位 Go 堆泄漏,则保留 MemStats 和 pprof。两个目标不同,采集源也应不同。

监控与修正的采用边界

实际落地可以记住三条:第一,用 ReadMemStats 解释 Go 堆和 GC,不把 Sys 当 RSS;第二,用操作系统或容器指标判断进程是否接近物理内存上限;第三,只有在两个指标同一时间点采集时,差值才有排查价值。Go 官方诊断文档也把 ReadMemStats 放在运行时统计,把 heap profile 放在定位分配来源的工具链中。

如果目标是进一步统一 Go runtime 侧的指标,可考虑 runtime/metrics;但它仍然不是跨平台 RSS API。对于“RSS 为什么比 MemStats 大”的问题,正确修正不是找一个神奇换算公式,而是先确认额外驻留页属于栈、runtime 元数据、cgo、mmap 还是共享映射,再选择对应工具。

常见追问

HeapSys 比 HeapAlloc 大很多就是内存泄漏吗

不一定。HeapSys 包含虚拟地址空间和可复用的堆区域,先看 HeapIdle、HeapReleased、GC 后的 HeapAlloc 趋势,再用 heap profile 定位长期存活对象。

为什么 RSS 降得比 HeapAlloc 慢

对象释放只说明 Go 堆不再需要这些对象;runtime 可能暂时保留空闲 span,操作系统也按页面回收和映射情况反映 RSS。应同时观察 HeapReleased、空闲差值和进程 RSS,不要只看单个采样点。

go
版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
Go trace 和 pprof 应该先看哪一个Go trace 和 pprof 应该先看哪一个
上一篇
Go trace 和 pprof 应该先看哪一个
Go GC 控制参数改了为什么延迟反而升高
下一篇
Go GC 控制参数改了为什么延迟反而升高
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    600次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    641次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    673次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    639次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    458次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议 和 隐私政策
返回登录
  • 重置密码