服务器入坑指北:内存口径打架时信谁

举报
Tulpartner 发表于 2026/10/26 08:37:47 2026/10/26
【摘要】 系统显示"还剩不少",服务却开始变卡;把每个进程的内存加起来,数字比整机内存还大,看着像统计坏了;同一台机器上两个工具给的两个数对不上;容器里看到的和宿主上看到的又不是一个数。这些困惑的根子是同一件事:内存有几个不同的"账本",它们回答的问题不一样,算出来的方式也不一样。把一个估算值当成精确值、把不同版本的"已用"直接拿来对比、把重复计数的总量当成真实占用——"口径打架"就是这么来的。

系统显示"还剩不少",服务却开始变卡;把每个进程的内存加起来,数字比整机内存还大,看着像统计坏了;同一台机器上两个工具给的两个数对不上;容器里看到的和宿主上看到的又不是一个数。
这些困惑的根子是同一件事:内存有几个不同的"账本",它们回答的问题不一样,算出来的方式也不一样。把一个估算值当成精确值、把不同版本的"已用"直接拿来对比、把重复计数的总量当成真实占用——“口径打架"就是这么来的。
这篇先讲清"你到底要回答哪个问题”,再给三本账的对照表、只读取证组与判定表,最后是"谁占着"的动作顺序与验证方法。示例一律用占位符,不贴任何真实读数,也不给可以照抄的阈值。

先问要回答哪个问题,再决定看哪个数

同一个"内存剩多少",其实对应两个完全不同的问题。先把问题定下来,数才不会选错:

  • “还能不能再开新服务、会不会开始换出” → 看可用量的估算。要注意它是估算,不是测量:它在替你把可以回收的那部分算进去了,所以它常常比"空闲"那一列大得多。它的正确用途就是回答这一个问题。
  • “谁占着内存” → 看按进程或按层级的分解。总量那一个数回答不了这个问题——它不告诉你是谁。
    "口径打架"的常见来源,就是把回答不同问题的两个数放在一起比大小:拿整机的可用量去减某个进程的占用、拿容器的数去比宿主的数、拿一个估算值去和另一个计数比。数没错,是比法错了。

三本账对照表

账本 回答什么问题 估算还是测量 不要把哪两个数直接比
整机(系统级) 还有多少能用、会不会换出 估算(可回收部分已经算进去了) 别拿它和"某个进程的占用"比
进程(按进程 / 按层级分解) 谁占着、占了多少 计数(但共享页会被重复计数) 别把常驻量加总当整机占用;也别和整机可用量比
容器 / 层级(cgroup) 这个层级用了多少 记账(文件缓存也计入) 别拿容器里的数和宿主整机的数直接比
还有一条必须点明的前提:同一个"已用",在不同工具版本或发行版上可能来自不同公式——有的把缓存算进"已用",有的干脆把它定义成"总量 − 可用量估算"。
所以正确的动作是:先确认你手上这个数是"怎么算出来的",再拿它做比较。跨版本、跨机器直接对比数值,很容易得出"这台机器更省"这类错误结论。(具体是哪个版本改的,以你机器上的说明为准。)

为什么加总会超过物理内存

这是全文最值得记住的一条机制:每个进程的常驻量对共享页重复计数。N 个进程共享同一个库时,每个进程都会把这个库算进自己的常驻量里。于是"把所有进程加起来"这个动作,天然会把同一块物理内存算上很多遍——加总超过物理内存是正常现象,不是统计坏了。
正确的口径是按比例分摊的那一个:共享页按共享者数量均摊——只属于这个进程的页全部计入,与别人共享的页只计入它应得的那一份。只有这个口径的加总,才与物理使用对得上。
这个口径还有两个配套能力:

  • 按类型拆分:把常驻量分成匿名、文件映射、共享内存几类——用来判断"占着的是什么性质",是堆栈在涨、还是文件映射在涨、还是共享内存。
  • 聚合视图:较新的内核直接提供整机的按比例分摊汇总,避免逐个进程解析的开销;较老的内核没有这个视图,需要自己汇总。做法是:对每个进程取按比例分摊的合计值再相加(用明细文件的合计行,或等价的按进程汇总),不要把常驻量直接相加。
    顺带解释另一种"对不上":缓存类的那一列包含共享内存(用内存做盘的临时文件系统那类也算在里面)。所以"总量 = 已用 + 空闲"这种直觉算式经常不成立;而且可用量与缓存量本身也不相等(一个是估算、一个是计数)。
    判据一句话:别用算术直觉校验系统计数,要用口径说明校验。

容器里是另一本账

容器(层级)视角下的用量,与"每进程常驻量之和"本来就可能不一致:这个视角把文件缓存也计进这一层,而进程视角看不到同一笔账。另外要记住:用内存做盘的临时文件系统,同样是真实压力——它不会因为"缓存不算"这种想法而被豁免。
所以第一步永远是:确认你看的是哪一本账——整机视角、进程视角、还是层级(容器)视角。三者回答的是不同问题,数值不一样是正常的。
再补一条与动作直接相关的前提:这台机器的总内存是由机型决定的。所以当结论是"内存真的不够"时,动作是改机型、迁移、降载,而不是"加内存条";而可用量估算只用于判断"会不会换出",不要拿它当"谁占着"的依据。

只读取证命令组

# 只读观测(示例输出一律用占位符,不贴真实读数)
free -m              # 整机视角:可用量估算那一列 + 缓存类那一列(看列名,别记数字)
cat /proc/meminfo    # 原始字段:可用量估算、缓存类、共享类等
# 进程视角
ps -eo pid,rss,comm --sort=-rss | head      # 常驻量排行(注意:这不是"加总"口径)
cat /proc/<pid>/status | grep -i -E 'VmRSS|RssAnon|RssFile|RssShmem'   # 按类型拆分的常驻量
cat /proc/<pid>/smaps_rollup 2>/dev/null    # 按比例分摊口径(较新内核);较老内核需自己汇总
# 容器 / 层级视角
cat /sys/fs/cgroup/memory.current 2>/dev/null \
  || cat /sys/fs/cgroup/memory/memory.usage_in_bytes 2>/dev/null

如果那两条 cgroup 路径都没有输出:先确认这个环境是否启用了内存控制器、以及你看的进程是否真的属于某个层级——这属于"我看的是哪本账"的一部分,不是"内存没问题"。
判据:每一条命令都要能回答"我看的是哪本账",并把它的性质对上号——估算 / 计数 / 记账。分不清性质,后面的比较就没有意义。

问题到动作的判定表

问题 / 现象 看哪本账的哪个数 结论 动作
“还剩多少” 整机的可用量估算 这是估算,用于判断会不会换出 只拿它回答"能不能再开新服务"
“谁占着” 按比例分摊的按进程分解(含按类型拆分) 找出大头与它的性质 按顺序:找大头 → 判性质 → 才动作
“两个数对不上” 先确认两个数各自怎么算出来的 多半是口径不同(估算 vs 计数) 改成同一性质再比:进程侧统一用按比例分摊口径加总;要与层级记账比时,先各自说明是"计数"还是"记账",并且只比同一层级内的变化
“进程加总超过物理内存” 进程常驻量(共享页被重复计数) 正常现象 改用按比例分摊的口径看加总
“容器与宿主不一致” 先确认看的是哪一本账 两本账本来就不是同一个数 按层级视角排查,别拿宿主的数当容器的数

"谁占着"的动作顺序

顺序本身就是判断,三步:

  1. 先看按比例分摊的排行,找出占用的大头——这一步才回答"谁占着";
  2. 再看按类型拆分,判断它是堆栈、文件映射还是共享内存——这一步决定"性质",也就决定了能不能降、值不值得降;
  3. 最后才决定动作:降载(限制并发、调小缓存之类)、调机型(把机器换大)、还是查泄漏(看它是不是在持续上涨)。
    判据:每一步都能说清"这一步回答什么、下一步基于什么"。上来就看总量、再凭感觉动手,等于跳过了前两步。

改完怎么验证:每项一条判据

  • 同一口径前后对比:不要跨口径比——改了配置又换了工具,等于没比。
  • 同一进程代际对比:重启会换进程号、计数器也归零,别把"重启后变好"当成优化效果。
  • 区分"缓存变多"是正常还是真占用:缓存类增长通常属于可回收的那一类(正常);如果是那类用内存做盘的东西在涨,那才是真实压力。
  • 再看一次可用量估算:它只用于判断"会不会换出",不作为"谁占着"的依据。
  • 回滚路径:改动前记下原值(配置或机型),验证不通过就还原再复测。
  • 验证方式固定成一句话:在同一口径、同一类对比下,改动前后的数能对上,才算通过。

这篇不解决什么

  • 不写换出机制:讲 zswap 的配置取舍那篇在换出层,并且主动把"内存读数口径"划给了本篇——所以本篇不写换出机制、不写压缩池参数,只解决"同一块内存为什么有多个不同的数"。
  • 不处理限制与取证:cgroup 内存上限与 OOM 取证那类内容在限制与取证层(怎么设限、被杀时怎么取证);本篇在读数口径层。
  • 不涉及 CPU 侧口径:《云盘变慢或报 I/O 错误时你能做什么》里讲的"窃取时间"是 CPU 侧的量;本篇只处理内存侧,不重复 CPU 部分。

踩坑清单

  • 拿总量那一个数回答"谁占着":它不告诉你是谁。
  • 把可用量估算当成精确值:它是替你把可回收部分算进去之后的估算。
  • 用"总量 = 已用 + 空闲"去校验系统计数:缓存类那一列包含共享内存,算式常不成立。
  • 跨版本、跨发行版直接比"已用":公式可能变过,先确认怎么算的。
  • 把进程常驻量加总当成整机占用:共享页被重复计数,加总会偏大。
  • 不知道共享页会被重复计数,于是以为统计坏了。
  • 拿宿主上的数当容器里的数:两者不是同一本账。
  • 把用内存做盘的临时文件系统当成"缓存不算":它是真实压力。
  • 重启之后计数器归零,就把变化当成优化效果。
  • 只盯一个数就下结论,不做同口径对比。
  • 觉得"加总对不上物理内存"是异常:按进程口径本来就会这样。
  • 结论落在"内存不够"时去想办法加内存条:虚拟化下总内存由机型决定。
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。