首页
/
行业洞察
/
正文
INDUSTRY INSIGHT · 深度
Linux内核内存泄漏排查:从free告警到meminfo与slabinfo实战
📅 2026/10/17 12:45:21
✍️ 爱科研究院
👁 阅读 3,247
1. 从一次线上告警说起为什么free内存充足却频繁OOM凌晨两点被电话叫醒监控面板上free -m显示还有3个G的可用内存但业务进程已经被内核OOM Killer干掉了。这种内存明明够用却报内存不足的场景做过几年运维或后端开发的人大概率都遇到过。第一反应通常是去看应用日志、查堆内存、翻GC记录折腾一圈发现应用层看起来干干净净问题却依然存在。这时候真正该盯的不是应用层而是内核层。Linux的内存管理是一个多层账本系统free命令看到的只是最外层的一个快照它把大量内核内部消耗的内存都算进了已用里但不会告诉你这些内存到底被谁拿走了。/proc/meminfo和/proc/slabinfo这两个文件才是深入内核内存账本的两把钥匙。这篇内容适合谁看一是遇到内存缓慢增长、重启能缓解但找不到根因的运维和SRE二是写内核模块或驱动、怀疑自己代码有泄漏的开发者三是对Linux内存机制感兴趣、想把free、meminfo、slab这几层关系彻底搞明白的技术人。我会从实际排查链路出发把meminfo的字段含义、slab的统计逻辑、以及如何用它们交叉定位泄漏点讲透中间穿插我自己踩过的坑和验证方法。需要先明确一个前提内存泄漏在Linux语境下分两种一种是用户态进程的堆泄漏比如malloc了没free另一种是内核态的泄漏比如kmalloc了没kfree、slab对象只增不减。前者用valgrind、heaptrack这类工具能搞定后者才是meminfo和slab的主战场。本文聚焦后者因为这类问题最隐蔽、最难查也最容易在半夜把你叫醒。2. 读懂/proc/meminfo别只盯着MemFree那一行2.1 MemFree、MemAvailable、Buffers、Cached到底谁说了算很多人看内存只看MemFree这是最大的误区。MemFree表示的是完全未被使用的物理内存但Linux的设计哲学是空闲内存就是浪费所以它会尽可能把内存拿去做页缓存page cache。你看到MemFree很小不代表内存紧张。真正该看的是MemAvailable。这个字段是内核估算出来的在不触发swap的前提下还能给新应用分配多少内存它把大部分可回收的页缓存和slab可回收部分都算进去了。判断内存是否真的吃紧MemAvailable比MemFree靠谱得多。# 一个典型的meminfo片段 MemTotal: 16384000 kB MemFree: 204800 kB MemAvailable: 8192000 kB Buffers: 102400 kB Cached: 6144000 kB Slab: 2048000 kB SReclaimable: 1536000 kB SUnreclaim: 512000 kB这里有个关键点Slab SReclaimable SUnreclaim。SReclaimable是内核可以回收的slab内存比如dentry缓存、inode缓存内存紧张时会被释放SUnreclaim是内核无法回收的slab内存这部分才是真正钉死在内存里的。如果你的SUnreclaim持续增长且不回落基本可以锁定内核slab泄漏。2.2 用watch盯住关键字段的变化趋势单次快照说明不了问题泄漏的本质是趋势。我习惯用下面这条命令持续观察watch -n 5 grep -E MemAvailable|Slab|SReclaimable|SUnreclaim|KernelStack|PageTables /proc/meminfo重点盯三个方向SUnreclaim是否单调递增、KernelStack是否异常膨胀线程泄漏的典型信号、PageTables是否持续走高大量mmap或内存映射泄漏。如果SUnreclaim在业务低峰期也不下降那基本可以排除是正常缓存行为往泄漏方向查。提示观察周期至少覆盖一个完整的业务波峰波谷短时间内的波动可能只是缓存预热别急着下结论。2.3 那些容易被忽略的隐形内存项除了Slabmeminfo里还有几个字段经常被忽视但往往是泄漏的真凶KernelStack每个内核线程占用的栈空间如果线程只创建不回收这个值会线性增长。PageTables页表本身占用的内存大量小内存映射或频繁mmap/munmap会导致它膨胀。VmallocUsedvmalloc区域的使用量某些驱动用vmalloc分配大块内存泄漏时这里会很明显。Percpu每CPU变量占用的内存和CPU数量相关一般稳定异常增长说明有per-cpu分配泄漏。我遇到过一次案例SUnreclaim平稳但KernelStack每小时涨几十KB最后定位到是某个内核模块反复创建workqueue线程却没销毁。所以排查时不要只盯Slab把这几项一起纳入观察范围。3. 解剖/proc/slabinfo找到那个只增不减的缓存3.1 slabinfo的列到底在说什么/proc/slabinfo的原始输出对新手不太友好建议先看/proc/slabinfo的格式化版本或者用slabtop。先理解列含义# 原始格式前几列 # name active_objs num_objs objsize objperslab pagesperslab kmalloc-64 10240 10240 64 64 1 dentry 512000 520000 192 21 1关键列解读列名含义排查价值active_objs当前活跃对象数持续增长说明对象被持有未释放num_objs总分配对象数与active_objs的差值反映空闲对象objsize单个对象大小结合对象数估算总内存占用objperslab每个slab页容纳的对象数用于计算slab页数量计算某个缓存占用的总内存num_objs * objsize / 1024得到KB数。比如dentry有520000个对象每个192字节就是约95MB。3.2 用slabtop快速排序定位大户slabtop是排查slab问题的第一把利器它能按内存占用或对象数排序几秒钟就能看出谁是大户# 按内存占用排序每2秒刷新一次 slabtop -s c -d 2 # 按对象数排序 slabtop -s n -d 2-s c按缓存大小排序-s n按对象数量排序。我一般先按大小排找到占用最高的几个缓存然后观察它们的ACTIVE和NUM是否在增长。如果某个缓存的NUM持续上涨而ACTIVE不降那就是泄漏嫌疑对象。3.3 区分正常增长和真泄漏不是所有增长的slab都是泄漏。dentry和inode缓存会随着文件访问自然增长这是正常的而且它们属于SReclaimable内存紧张时会被回收。真正要警惕的是SUnreclaim里的缓存以及那些名字看起来就很业务相关的自定义缓存。判断方法在业务低峰期比如凌晨没有请求时观察。如果缓存能回落说明是正常缓存如果纹丝不动甚至继续涨那就是泄漏。我通常会写个小脚本每隔10分钟记录一次目标缓存的num_objs跑一晚上看趋势曲线。# 记录kmalloc-256的对象数变化 while true; do date /tmp/slab_trace.log grep kmalloc-256 /proc/slabinfo /tmp/slab_trace.log sleep 600 done4. 把meminfo和slabinfo串起来一条完整的定位链路4.1 第一步确认泄漏发生在内核态还是用户态先看SUnreclaim的趋势。如果它持续增长基本锁定内核态slab泄漏直接进slabinfo排查。如果SUnreclaim平稳但MemAvailable持续下降那要往用户态进程查用ps aux --sort-rss找RSS最高的进程再结合/proc/pid/smaps分析。还有一种情况SUnreclaim和用户态RSS都正常但KernelStack或PageTables异常那就往线程泄漏或内存映射泄漏方向查。4.2 第二步在slabinfo里锁定嫌疑缓存确认是slab泄漏后用slabtop -s c找到占用最高的几个缓存然后对每个嫌疑缓存做趋势观察。重点关注两类通用缓存kmalloc-*系列。如果kmalloc-256这类通用缓存持续增长说明有代码在用kmalloc分配但没释放但具体是谁分配的不好定位。专用缓存名字带业务前缀的比如my_driver_ctx、foo_cache。这类缓存是kmem_cache_create创建的一旦泄漏名字直接指向模块定位效率极高。提示如果怀疑是某个内核模块可以先lsmod看加载了哪些模块再对照slabinfo里的缓存名很多模块会用模块名做缓存前缀。4.3 第三步用tracepoint抓分配调用栈锁定嫌疑缓存后光知道谁在涨还不够得知道谁在分配。这时候要用到内核的slab tracepoint。以kmalloc为例# 开启kmalloc的tracepoint echo 1 /sys/kernel/debug/tracing/events/kmem/kmalloc/enable # 抓一段时间后关闭 echo 0 /sys/kernel/debug/tracing/events/kmem/kmalloc/enable # 查看结果 cat /sys/kernel/debug/tracing/trace | head -100trace里会记录每次分配的调用栈如果某个调用栈出现频率极高且对应的是只分配不释放的路径那就是泄漏点。这个方法需要内核开启了CONFIG_TRACEPOINTS和debugfs生产环境不一定有但测试环境复现时非常有效。4.4 第四步交叉验证排除误判定位到嫌疑点后别急着改代码先做交叉验证。方法包括卸载可疑模块看SUnreclaim是否停止增长、用kmemleak做一次全量扫描、或者在测试环境构造相同负载复现。kmemleak是内核自带的泄漏检测工具需要内核编译时开启CONFIG_DEBUG_KMEMLEAK。开启后它会周期性扫描内存把疑似泄漏的地址和调用栈记录在/sys/kernel/debug/kmemleak里。虽然有一定误报率但作为交叉验证手段非常有用。# 触发一次扫描 echo scan /sys/kernel/debug/kmemleak # 查看疑似泄漏 cat /sys/kernel/debug/kmemleak5. 几个真实场景的排查复盘5.1 场景一kmalloc-256缓慢增长最后定位到网络收包路径某次线上服务运行一周后SUnreclaim涨了800MBslabtop显示kmalloc-256是最大户。用tracepoint抓了一分钟发现大量分配来自网络驱动的收包路径。进一步查代码发现驱动在处理某种异常包时分配了skb但错误路径没释放。修复后SUnreclaim稳定在200MB左右。这个案例的教训是通用缓存泄漏最难定位因为kmalloc-256被无数代码路径共用必须靠tracepoint抓调用栈才能缩小范围。5.2 场景二自定义缓存只增不减模块卸载后内存不回收另一个案例是某内核模块创建了自定义slab缓存但模块卸载时没有销毁缓存导致内存永久占用。slabinfo里能看到这个缓存名但模块已经rmmod了缓存还在。这种情况只能重启或者写个脚本手动清理不推荐风险高。正确的做法是在模块的exit函数里调用kmem_cache_destroy并且确保所有对象都已释放。我后来在代码review清单里加了一条任何kmem_cache_create必须配对kmem_cache_destroy。5.3 场景三dentry缓存暴涨其实是正常的有次看到dentry缓存占了2GB吓了一跳结果发现是业务在遍历大量小文件dentry缓存自然增长。因为dentry属于SReclaimable手动echo 2 /proc/sys/vm/drop_caches就能释放。所以看到缓存大不要慌先确认它在SReclaimable还是SUnreclaim里。6. 排查工具与参数速查6.1 常用命令清单命令用途watch -n 5 grep ... /proc/meminfo持续观察内存趋势slabtop -s c -d 2按大小排序查看slabslabtop -s n -d 2按对象数排序查看slabcat /proc/slabinfo原始slab数据echo scan /sys/kernel/debug/kmemleak触发kmemleak扫描echo 2 /proc/sys/vm/drop_caches释放可回收缓存6.2 关键内核配置项排查slab泄漏前确认内核开启了这些配置CONFIG_SLUB_DEBUGslub调试、CONFIG_DEBUG_KMEMLEAKkmemleak、CONFIG_TRACEPOINTStracepoint、CONFIG_DEBUG_FSdebugfs。生产内核为了性能通常不开这些所以线上定位靠趋势观察测试环境复现靠这些工具。6.3 我个人的排查习惯我一般会先花10分钟做趋势观察确认是内核态还是用户态问题再决定用哪套工具。最忌讳一上来就上重型工具既慢又可能干扰生产。趋势观察阶段用watch加slabtop就够了锁定嫌疑对象后再上tracepoint或kmemleak。另外记录很重要。我会把每次观察的时间、SUnreclaim值、top缓存名记在一个表格里跑几个小时就能看出规律。有次就是靠这个表格发现泄漏和某个定时任务强相关直接缩小了排查范围。最后分享一个小技巧如果怀疑是某个模块可以在测试环境用rmmod卸载它然后观察SUnreclaim是否停止增长。这个方法简单粗暴但非常有效能快速排除或确认模块嫌疑。当然生产环境慎用卸载正在使用的模块可能导致业务中断。
📌 标签:
工业官网
设计趋势
AI 建站
SEO
获取完整报告 →
RELATED ARTICLES
推荐阅读
2026/10/17 12:40:21
PyCharm 接入 DeepSeek:用 Continue 插件实现 AI 代码补全与对话调试
2026/10/17 12:40:21
把 Cursor Base URL 改到 TaoToken:让 GitHub 神器变成你的私人下载助理
2026/10/17 12:40:21
2026程序员武器库更新:5款AI编程助手横向评测,TaoToken统一Key接入实测
2026/10/17 13:35:25
前端学习底层逻辑:从解决问题出发,构建稳固技术地基
2026/10/17 13:35:25
本地27B大模型提速10倍:投机解码调优实战
2026/10/17 13:35:25
Fisher线性判别实战:小样本高解释性分类的工程落地指南
2026/10/17 13:35:25
CANN 算子原型注册详解:使用 OpAICoreConfig::Input 按 AI 处理器型号差异化注册算子输入
2026/10/17 13:35:25
Spring Boot + Vue 家用电器销售网站56930
2026/10/17 13:30:25
shadcn/ui源码型组件库:从黑盒依赖到可拥有的前端开发模式
2026/10/17 0:02:58
NetSpot 实战:用信道分析与频宽优化解决 Wi-Fi 卡顿
2026/10/17 0:02:58
C++ split() 字符串分割的五种实现与性能实测
2026/10/17 0:02:58
R语言坐标轴范围设置:从xlim/ylim到coord_cartesian的完整指南
2026/10/17 10:00:11
流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南
2026/10/17 10:16:23
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别
2026/10/17 10:16:02
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容
2026/10/17 10:15:50
我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频
2026/10/16 18:46:48
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证
2026/10/16 18:46:46
2026 大模型集体涨价:用 Python 做企业 Token 成本测算与选型避坑(附配置)