当前位置:首页 > 文章列表 > 文章 > linux > Linux cgroup v2 memory.stat 怎样区分匿名页和文件页

Linux cgroup v2 memory.stat 怎样区分匿名页和文件页

来源:17golang原创 2026-10-25 14:14:42 0浏览 收藏

排查 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 是只读的扁平键值文件,数值单位是字节。内核文档特别提醒:字段顺序可能在中间新增,程序应通过键名寻找字段。这个规则看似简单,却是很多临时排障脚本最容易忽略的地方。

memory.stat 将 cgroup 内存足迹拆分为匿名映射、文件缓存和内核开销的说明图
图1: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 等事件。

从 memory.current、memory.stat 到文件缓存、匿名内存和事件计数分支的排查路径说明图
图2:从 cgroup 内存总量到字段组合判断的排查路径说明图;这是静态结构图,不是运行截图或故障证据。
观察组合优先追问
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 事件,应该单独解释。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
io_uring SQPOLL 怎样理解轮询行为io_uring SQPOLL 怎样理解轮询行为
上一篇
io_uring SQPOLL 怎样理解轮询行为
epoll EPOLLONESHOT 怎样正确重新武装事件
下一篇
epoll EPOLLONESHOT 怎样正确重新武装事件
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之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模型性能。
    686次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    701次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    757次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    719次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    557次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议 和 隐私政策
返回登录
  • 重置密码