服务器入坑指北:内存泄漏的观察与处置顺序

举报
Tulpartner 发表于 2026/11/07 08:59:11 2026/11/07
【摘要】 内存一点点涨,重启能缓一阵,过几天又涨回去;一看就说是"泄漏",其实可能是缓存;也不知道什么时候该重启、什么时候该先取证。 在你下"泄漏"这个结论之前,先记住这条前提:单次读数说明不了任何事——"在涨"必须由两次读数之间的差来证明;而**"涨的是哪一类"决定它是不是泄漏**。

内存一点点涨,重启能缓一阵,过几天又涨回去;一看就说是"泄漏",其实可能是缓存;也不知道什么时候该重启、什么时候该先取证。
在你下"泄漏"这个结论之前,先记住这条前提:单次读数说明不了任何事——"在涨"必须由两次读数之间的差来证明;而**"涨的是哪一类"决定它是不是泄漏**。
这篇给三段可直接粘贴的内容:取两次算速率 → 分流与算到顶 → 处置顺序(第三段含写操作,顺序不能反)。取值用占位符,字段名、命令名与配置键照写。

用速率,不要用单次读数

增长速率 = Δ常驻内存 / Δ时间——两次读数相减,再除以两次取值的间隔秒数。

  • 分子取常驻量(VmRSS 那一行,或用只出数值的形态);
  • 分母取两次时间戳之差(秒)。

取两次算速率块

date +%s                                      # 第一次:记下时间戳(秒)
cat /proc/<PID>/status                        # 第一次取样:整份状态,判据行是 VmRSS(常驻)与 VmSize(虚拟)
ps -o pid,rss,vsz,cmd -p <PID>                # 一眼对比:判据列是 rss(常驻,KB)与 vsz(虚拟)
grep -E 'VmRSS|VmSize' /proc/<PID>/status     # 第一次取样:判据是 VmRSS 那一行(虚拟量级只作对照)
ps -o rss= -p <PID>                           # 只输出 RSS 数值(KB),便于两次相减
date +%s                                      # 第二次:间隔后记时间戳
grep -E 'VmRSS|VmSize' /proc/<PID>/status     # 第二次取样:同一行再读一次

判据:持续为正且不回落,才算"在涨";单次读数高或者低,都不构成结论——没有分母,就没有速率。
算一遍(数值为演示值):第一次读到 512 MiB、第二次读到 768 MiB,两次取值相隔 3600 秒 ⇒ 增长速率 = (768 - 512) / 3600 ≈ 0.071(MiB 每秒),也就是约 256 MiB 每小时。
同一组数接着算第二步——还有多久到顶:
到顶时间 = (上限 − 当前) / 速率。
接着算:上限 2048 MiB、当前 768 MiB、速率按 256 MiB 每小时 ⇒ 到顶时间 = (2048 - 768) / 256 = 1280 / 256 = 5(小时)。
这两步的意义:它把"要不要现在处理"从感觉变成算式——到顶时间远大于你能接受的处理窗口,就可以先观察;否则先留现场再处置。("涨多少才算泄漏"的门槛按你自己的口径定,这里演示的只是怎么算。)

先分流:能回收的,与回不来的

本篇最值钱的一条:内存统计里页缓存与匿名内存是两类——

  • file(页缓存)通常可在内存压力下被回收:看到"涨"了,但压力上来它会退;
  • anon(匿名内存)涨上去往往回不来:需要交换或被杀才能释放。
cat /sys/fs/cgroup/<路径>/memory.stat          # 分流判据:file 对 anon(版本相关,以现读为准)
cat /sys/fs/cgroup/<路径>/memory.current       # 组级当前占用(版本相关)
cat /sys/fs/cgroup/<路径>/memory.max           # 上限:算"到顶时间"的分母侧减数(版本相关)
systemctl show <单元> -p MemoryMax             # 上限的服务级写法,输出 MemoryMax=<值>
free -h                                       # 全局对照:判据是 available 列(把可回收算进去后的可用量)

判据:先看这两类的构成变化,再决定要不要处置——anon 持续增长=偏向真泄漏;只有 file 增长=页缓存,不是泄漏。不分流就下"泄漏"结论,是这类问题最常见的误判。
共享页还要再看一层:cat /proc/<PID>/smaps_rollup 里的 Rss 含共享、Pss 把共享页按份额摊到本进程——两者差得多,说明占用里有相当一部分是共享的(该入口版本相关,以现读为准)。
内核侧也要排除一下:slabtop 看内核对象占用是否也在涨(它由 procps-ng 提供,未装则先装);段级分布用 pmap -x <PID>(判据列是各段的 RSS 与 Dirty,同样由 procps-ng 提供);宿主级视角用 systemd-cgtop(需在运行 systemd 的宿主上;容器内通常没有)。

处置顺序:先留现场,最后才重启

顺序是固定的,因为重启会把现场清掉:

  1. 先拿只读证据:段级分布(pmap -x <PID>)、两类构成(memory.stat 的 file 与 anon)、上限与当前值——这些一旦重启就都没了;
  2. 再算两式:速率与到顶时间(上面那两步);
  3. 再看"被杀过没有":这是独立于"在涨"的另一件事;
  4. 最后才重启或扩容(写操作,标回滚点、先记原值)。
cat /sys/fs/cgroup/<路径>/memory.events        # 判据键=oom_kill 是否非零/递增(版本相关)
valgrind --leak-check=full <程序>               # 处置顺序的最后一步:在可复现的测试进程上定位泄漏点

判据:

  • oom_kill 递增说明该层级确实杀过进程——它与"是否在涨"是两件事,两者都要看;
  • 定位工具是最后一步:valgrind --leak-check=full <程序> 需先装 valgrind,而且要在可复现的测试进程上跑——在生产进程上做跟踪类观测会影响被测对象;
  • 另一个容易混的错误码:分配失败由调用返回 ENOMEM(不是内核杀进程);内核杀进程才体现在 oom_kill 上——两者不要互相代入。

取舍:扩容、定位,还是先重启止损

  • 扩容(改 memory.max 或 MemoryMax=):争取时间;代价是把问题推迟,并没有解决;
  • 定位(在测试进程上用 valgrind):找到根因;代价是观测代价与时间;
  • 先重启止损:业务先恢复;代价是丢现场——除非业务已经不可用,否则不优先。
    判据:三条路的代价都要写出来;"先重启"只在业务不可用时才优先。上限的持久化落点是 MemoryMax=——重启后仍生效。

现象到动作的判定表

现象 结论 动作
内存一点点涨 需要用速率判定 取两次读数、算 Δ常驻 / Δ时间
涨的是文件缓存 到压力会退,不是泄漏 看 memory.stat 的 file;不必按泄漏处置
匿名内存回不来 偏向真泄漏 看 anon 的持续增长,按处置顺序走
重启缓一阵又涨回去 根因没被处理 先留现场再重启,并把两式算出来
到顶时间远大于处理窗口 可以继续观察 记下速率与到顶时间,设一个复查点

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

  • 速率由两次读数算出(不是单次读数);
  • 两类构成能解释"为什么涨"(file 还是 anon);
  • 到顶时间可复算;
  • 留存了只读证据(重启之后也能说清当时的样子);
  • 有回滚形态与原值记录。

这篇不解决什么

  • 讲cgroup v2 内存上限与 OOM 取证的那篇管限制层——上限设多少、OOM 之后怎么归因(“什么时候被杀”);本篇管增长类问题的观察与处置顺序(“涨不涨、怎么处置”)。判定入口:是"触顶被杀"还是"缓慢增长未见顶"。
  • 讲内存口径打架时信谁的那篇是读数口径之间的取舍;本篇是时间维度上的趋势——一个管"信哪个数"、一个管"这个数在往哪走"。
  • 讲进程与线程数上限的那篇在"执行单元数量"层;本篇在"内存占用趋势"层,两篇无重叠。

踩坑清单

  • 用单次读数下"泄漏"结论:没有速率。
  • 不分流就看总量:分不清页缓存与匿名内存。
  • 把文件缓存的涨当成泄漏:它到压力会退。
  • 先重启把现场清掉:顺序反了,可诊断的问题变成不可诊断。
  • 只算速率不算到顶时间:没法决定"现在动不动"。
  • 在生产进程上直接上定位工具:会影响被测对象。
  • 把"上限被撞"当成泄漏:那是限制层的问题,判据与入口都不同。
  • 把 ENOMEM 与 oom_kill 混为一谈:一个是调用返回的错误码,一个是内核杀进程的计数。
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

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

全部回复

上滑加载中

设置昵称

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

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

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