Linux cgroup v2 memory.stat 怎样区分匿名页和文件页
来源:17golang原创
时间:2026-10-25 14:14:42 405浏览 收藏
排查 Linux cgroup v2 的内存占用时,memory.stat 最适合回答“这部分内存大致属于哪一类”,而不是直接回答“哪个进程泄漏了”。它把 cgroup 的内存足迹拆成键值对:anon 代表匿名映射,file 代表文件系统缓存(也包括 tmpfs 和共享内存),kernel 则是内核内存总量。阅读时应按键名取值,不能按输出行号取值。
官方文档:https://docs.kernel.org/admin-guide/cgroup-v2.html
先用memory.current确认总量,再用memory.stat区分匿名映射、文件缓存和内核开销;如果文件类占比明显,再看file_mapped、file_dirty等细项,最后把结果与memory.events的事件计数分开解释。
memory.stat 到底统计了什么
memory.stat 是只读的扁平键值文件,数值单位是字节。内核文档特别提醒:字段顺序可能在中间新增,程序应通过键名寻找字段。这个规则看似简单,却是很多临时排障脚本最容易忽略的地方。

| 字段 | 可以怎样理解 | 不要怎样理解 |
|---|---|---|
| anon | 匿名映射使用的内存,例如 brk、sbrk 和匿名 mmap | 不等于某个进程的完整 RSS |
| file | 文件系统数据缓存,包括 tmpfs 与共享内存 | 不等于“已经写入磁盘的文件大小” |
| kernel | 内核内存总量,包含内核栈、页表、percpu、vmalloc、slab 等 | 不等于应用运行时的 native heap |
因此,看到 file 变大时,不要马上把结论写成“磁盘缓存泄漏”;看到 anon 变大时,也不要直接断言某个对象一定没有释放。它们是 cgroup 层面的分类线索,下一步还要结合进程、映射和业务行为。
按键名读取三个主分类
下面的示例只读取接口文件,不修改 cgroup 配置。把 /sys/fs/cgroup/workload.slice 换成目标 cgroup 的实际路径。用 awk 按第一个字段匹配,避免依赖文档输出的视觉顺序。
# 把路径替换成待排查的 cgroup;这里只做只读统计。
CGROUP="/sys/fs/cgroup/workload.slice"
# 按字段名读取,不能用 NR==1、NR==2 之类的行号假设。
awk '
$1 == "anon" { anon=$2 }
$1 == "file" { file=$2 }
$1 == "kernel" { kernel=$2 }
END {
printf "anon=%.2f MiB\n", anon / 1024 / 1024
printf "file=%.2f MiB\n", file / 1024 / 1024
printf "kernel=%.2f MiB\n", kernel / 1024 / 1024
}
' "$CGROUP/memory.stat"
脚本中的换算只是为了阅读方便,内核文件本身仍然以字节表示。字段缺失时,变量可能保持为空;在生产脚本中应为缺失字段设置默认值,并检查文件是否可读,而不是把空值当成零再下结论。
file 变大时继续看哪些细项
file 是文件类内存的总分类,不能单独说明来源。file_mapped 可以帮助观察被 mmap 映射的文件缓存,file_dirty 表示尚未写回磁盘的脏文件缓存,file_writeback 则表示正在写回的部分。它们服务于不同判断,不应简单相加后当成独立总量。
from pathlib import Path
# 只按 key=value 读取 memory.stat,忽略未知字段以兼容内核新增项目。
def read_memory_stat(path: str) -> dict[str, int]:
values: dict[str, int] = {}
for line in Path(path).read_text(encoding="utf-8").splitlines():
# 每行是字段名和字节数;异常行不参与分类。
parts = line.split()
if len(parts) != 2:
continue
key, raw = parts
try:
values[key] = int(raw)
except ValueError:
# 记录格式异常时跳过,不把异常文本伪装成数值。
continue
return values
stat = read_memory_stat("/sys/fs/cgroup/workload.slice/memory.stat")
# 用 get 兼容不同内核版本或权限环境下缺少的细项。
for key in ("anon", "file", "file_mapped", "file_dirty", "kernel"):
print(f"{key}: {stat.get(key, 0) / 1024 / 1024:.2f} MiB")
这段 Python 代码强调两个边界:新字段出现时不应导致解析器崩溃,字段不存在时也不能把默认值误写成“内核明确报告为零”。如果默认值会影响告警,应额外返回缺失字段集合。
把几个字段放回同一条排查链
单看 memory.stat 只能知道分类,排查还需要一个稳定顺序。先读 memory.current,确认 cgroup 当前使用量;再读 memory.stat,找出主要分类;如果 file 占比较高,再看 file_mapped、file_dirty 和写入业务;如果 anon 持续增长,再回到堆、匿名 mmap、线程栈和子进程生命周期;最后读 memory.events,判断是否出现 high、max、oom 或 oom_kill 等事件。

| 观察组合 | 优先追问 |
|---|---|
| anon 高且持续上涨 | 工作集、匿名 mmap、堆外分配或子进程是否持续增加 |
| file 高,file_mapped 也高 | 是否存在大量映射文件、共享库或 mmap 数据集 |
| file_dirty 高 | 写入是否积压,回写速度是否跟不上生产速度 |
| kernel 高 | 页表、内核栈、slab 或 socket 等开销是否随并发增长 |
| events 中 oom_kill 增加 | 是否已经发生组内 OOM,需结合日志确认受影响任务 |
不要把分类字段当成最终结论
第一,memory.current 和 memory.stat 都是 cgroup 视角;它们可能包含后代 cgroup 的贡献,具体要结合层级和内核接口语义解释。第二,file 包含 tmpfs 和共享内存,不能只按“磁盘文件缓存”理解。第三,memory.events 是事件计数,不是当前内存占用;事件增长与 anon、file 的大小之间没有一一对应关系。
如果服务由 systemd 或容器运行时管理,还应先确认正在看的 cgroup 路径属于哪一层。父级预算可能限制子组,进程迁移也不会把过去已经计入的内存自动改记到新组。读取统计时保留采样时间和 cgroup 路径,才能把多次结果放在同一上下文中比较。
速查:下一步应该看哪里
| 现象 | 下一步 |
|---|---|
| 只想知道总量 | 读取 memory.current,并记录 cgroup 路径 |
| 想区分匿名和文件类 | 读取 memory.stat 的 anon、file、kernel |
| 文件类占比高 | 继续看 file_mapped、file_dirty、file_writeback |
| 怀疑触及限制 | 单独读取 memory.events,区分 high、max、oom、oom_kill |
| 结果随时间变化 | 按固定间隔采样,比较增量,不用一次读数定性 |
memory.stat 的字段顺序固定吗?
不固定。应按字段名寻找项目,不能按行号解析;内核可能在中间增加新字段。
file 越大就一定是缓存没释放吗?
不一定。file 包含文件系统缓存、tmpfs 和共享内存,还要结合 file_mapped、file_dirty 以及业务行为判断。
anon 是否就是应用堆?
不是。anon 覆盖匿名映射,应用堆只是其中可能的一部分,还可能包含匿名 mmap、线程栈等。
应该先读 stat 还是 events?
建议先用 current 确认总量,再用 stat 做类型拆分;events 负责说明限制或 OOM 事件,应该单独解释。
-
501 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
478 收藏
-
451 收藏
-
294 收藏
-
263 收藏
-
279 收藏
-
200 收藏
-
203 收藏
-
425 收藏
-
487 收藏
-
442 收藏
-
128 收藏
-
342 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习