资讯动态

Linux内存排查实战:free与ps对不上,20G内存去哪了?

发布时间:2026/10/20 6:02:29 来源:尧图企业网站定制
1. 内存都去哪了一次典型的“幽灵占用”排查实录线上跑着的一台业务机监控突然告警说可用内存跌破阈值。我登上机器习惯性地敲下free -h结果确实不太好看total 32Gused 29Gfree 只剩几百兆available 也就 1G 出头。按经验这种量级的占用要么是某个 Java 服务堆开太大要么是数据库缓存吃满了。于是我顺手ps aux --sort-%mem | head准备揪出那个“内存大户”。结果出乎意料。排在前面的进程 RSS 加起来也就 6G 左右剩下二十多个 G 像是凭空蒸发了。top里所有进程的 RES 列加一遍也对不上账。这种“free 说没内存ps 说没人用”的割裂感是很多运维和开发都踩过的坑。它不是什么灵异事件而是 Linux 内存管理机制在特定场景下给你的一个“障眼法”。这篇内容就是把这个障眼法拆开讲清楚。我会从内存统计的底层逻辑说起解释为什么会出现“可用物理内存很少但进程占用不大”的现象然后给出几套可复现的排查命令和判断路径最后把常见成因整理成速查表。适合有一定 Linux 基础、正在被内存问题困扰的开发和运维同学小白也能跟着命令一步步看下去。2. 先搞懂 free 到底在统计什么2.1 free 的字段含义与常见误读很多人看free只看 free 那一列一看数字小就慌。其实free命令输出的每一列含义差别很大误读往往就出在这里。先看一个典型输出total used free shared buff/cache available Mem: 31Gi 29Gi 512Mi 1.2Gi 1.5Gi 1.1Gi Swap: 8.0Gi 2.0Gi 6.0Gi这里有几个关键点。free列指的是完全未被使用的物理内存这个数字小其实很正常因为 Linux 的设计哲学就是“空闲内存是浪费”它会主动把没用到的内存拿去做缓存。真正该关注的是available列它表示在不触发 swap 的前提下应用程序还能申请到多少内存这个值才是判断内存是否真的紧张的指标。buff/cache是块设备缓存和文件系统缓存的合计。这部分内存在应用需要时可以被立即回收所以它不算真正的“被占用”。shared是 tmpfs 等共享内存的占用这部分比较特殊它既算在 used 里又不会体现在普通进程的 RSS 中是“幽灵占用”的头号嫌疑对象。提示判断内存是否真的吃紧看 available不要看 free。available 长期低于总内存的 10%才需要认真排查。2.2 used 的构成为什么它和进程 RSS 对不上used这个字段的计算方式是total - free - buff/cache。注意它不等于所有进程 RSS 之和。这中间的差额来自好几个地方内核自身的数据结构slab、页表、驱动占用的内存、tmpfs 共享内存、以及被进程申请但尚未真正写入物理页的匿名内存等。换句话说used是一个“系统级”的统计口径而ps看到的 RSS 是“进程级”的口径两者本来就不是一回事。当你发现 used 远大于进程 RSS 总和时差额大概率藏在下面这几个地方tmpfs / shm挂在/dev/shm、/run等位置的共享内存占用算进 used但不属于任何进程的 RSS。内核 slab内核对象缓存比如 dentry、inode 缓存用slabtop能看到。页表与内核栈进程越多、内存越大页表开销越大这部分也不计入 RSS。内存碎片与预留某些驱动或大页配置会预留内存free 看不到但实际不可用。理解了这层你就明白为什么“free 少但进程不大”是常态而非异常。接下来要做的是把这个差额具体定位到某一类占用上。3. 逐层排查把消失的内存找出来3.1 第一站确认是不是缓存和共享内存排查的第一步永远是先排除“假警报”。执行free -h看 available如果 available 还有好几个 G那基本不用慌是 buff/cache 在撑场面。如果 available 确实很低再往下走。接着看 shared 这一列。如果 shared 有几个 G那基本可以锁定是 tmpfs 或共享内存的问题。用下面这条命令列出所有 tmpfs 挂载点及其占用df -h -t tmpfs常见的“重灾区”是/dev/shm。有些应用比如某些数据库、消息队列、或者用了共享内存的中间件会往这里写大量数据进程退出后如果没清理干净内存就一直挂着。我遇到过好几次某个服务异常退出/dev/shm里留了几个 G 的残留文件free 一直上不来重启服务也没用最后手动清理才恢复。ls -lh /dev/shm如果发现大文件且确认无进程使用可以安全删除。但删之前一定要确认没有活着的进程还在引用它否则可能引发数据损坏。3.2 第二站用 smem 看真实的进程内存分布ps和top的 RSS 有个天然缺陷它会把共享库的内存重复计算。比如十个进程都链接了同一个 50M 的库每个进程的 RSS 都会算上这 50M加起来就虚高了。反过来有些内存又不计入任何进程导致总和偏低。要看更真实的数据推荐用smemsmem -t -k -s rsssmem会给出 PSS按比例分摊共享内存后的值和 USS进程独占内存这两个指标比 RSS 靠谱得多。把所有进程的 PSS 加起来再和 used 对比差额就清晰多了。如果 PSS 总和依然远小于 used那问题基本不在用户态进程而在内核或共享内存。3.3 第三站内核 slab 与页表开销内核态的内存占用ps是完全看不到的。用slabtop可以查看内核对象缓存的分布slabtop -s c -o按缓存大小排序重点看dentry、inode_cache、ext4_inode_cache这几项。如果它们加起来有好几个 G说明文件系统元数据缓存偏大。这部分内存理论上可回收但在内存压力大时回收不及时就会表现为 available 偏低。页表开销可以用pmap汇总或者直接看/proc/meminfo里的PageTables字段grep -E PageTables|Slab|Shmem|KernelStack /proc/meminfo这几个字段是定位“幽灵内存”的黄金指标。Shmem对应共享内存Slab对应内核缓存PageTables对应页表KernelStack对应内核栈。把它们加起来往往就能解释掉大部分差额。3.4 第四站被忽略的匿名内存与内存泄漏如果上面几项都正常但 used 依然高企那就要怀疑匿名内存了。匿名内存是进程通过 malloc 申请、但没有对应文件映射的内存。它会计入进程的 RSS但如果进程申请后没实际写入或者写入后被 swap 出去统计上就会有偏差。还有一种情况是内存泄漏。进程持续申请内存但不释放RSS 会缓慢增长。这种问题用ps定时采样就能发现while true; do ps -o pid,rss,comm -p PID; sleep 60; done如果 RSS 只涨不跌基本可以确认泄漏。这时候需要结合pmap看具体是哪段内存增长pmap -x PID | sort -k3 -n -r | head4. 实操复盘一次 20G 内存消失的完整定位过程4.1 现场信息采集回到开头那台机器。我按下面的顺序采集了一轮信息这套流程现在已经是我的标准动作free -h grep -E MemTotal|MemFree|MemAvailable|Buffers|Cached|Shmem|Slab|PageTables|KernelStack /proc/meminfo df -h -t tmpfs smem -t -k -s rss | tail -5 slabtop -s c -o | head -20采集完一对比线索就出来了。/proc/meminfo里Shmem显示 18G而df -h -t tmpfs显示/dev/shm占了 17G 多。进程 RSS 总和才 6Gslab 也就 1G 出头。答案基本锁定/dev/shm里有大量残留数据。4.2 定位到具体文件与进程进/dev/shm一看果然有几个大文件命名像是某个中间件的临时数据。用lsof反查哪些进程还在引用lsof /dev/shm/* 2/dev/null结果发现只有一个已经处于僵尸状态的进程还挂着文件描述符实际业务进程早就重启过了。也就是说这些共享内存段成了“孤儿”既没有进程正常使用也没被系统回收。4.3 安全清理与验证清理前我做了两件事一是确认这些文件确实没有活跃进程在读写通过lsof和fuser双重确认二是备份了文件名和大小记录以防后续需要追溯。然后执行删除fuser -m /dev/shm/filename rm -f /dev/shm/filename删完再free -havailable 立刻从 1G 回升到 19G。整个过程没有重启任何服务业务无感知。注意清理/dev/shm一定要谨慎。如果确实有活跃进程在用删除文件会导致该进程后续读写失败甚至崩溃。稳妥做法是先停掉相关服务再清理最后重启服务。4.4 根因分析与长效预防事后复盘根因是那个中间件在异常退出时没有正确释放共享内存而它的启动脚本又没有做清理逻辑。我们做了两件事来预防一是在服务启动脚本里加上清理旧共享内存段的逻辑二是给/dev/shm单独加了监控告警占用超过阈值就通知。这里有个经验/dev/shm默认大小通常是物理内存的一半很多人不知道它可以调整。如果业务确实需要大共享内存可以在/etc/fstab里显式配置大小避免它无限制增长影响系统。5. 常见成因速查与避坑指南5.1 成因速查表把这次排查和以往遇到的案例整理成一张表方便对照现象可能成因定位命令处理方式available 低free 低shared 高tmpfs/共享内存残留df -h -t tmpfs、ls /dev/shm确认无引用后清理used 高进程 RSS 总和小内核 slab 偏大slabtop -s c可手动回收或调参used 高PageTables 大进程数多或大内存进程grep PageTables /proc/meminfo减少进程数或优化某进程 RSS 持续增长内存泄漏定时ps采样修复代码或重启available 低但无明显占用内存碎片/预留/proc/meminfo全字段评估是否需重启5.2 几个容易踩的坑第一个坑是盲目重启服务。很多人一看内存高就重启业务结果重启完内存没降因为占用根本不在业务进程里。重启反而造成业务中断得不偿失。正确做法是先定位再动手。第二个坑是误删共享内存。/dev/shm里的文件看着像临时文件但可能有活跃进程在用。删之前务必用lsof或fuser确认。我见过有人直接rm -rf /dev/shm/*结果把正在运行的数据库搞崩了。第三个坑是把 buff/cache 当成占用。buff/cache 是可回收的看到它大不用慌。真正要盯的是 available。有些监控系统直接把 used 当告警指标这是不准确的容易误报。第四个坑是忽略 cgroup 限制。在容器环境里free看到的是宿主机内存容器实际可用内存受 cgroup 限制。这时候要看/sys/fs/cgroup/memory/memory.limit_in_bytes和memory.usage_in_bytes而不是宿主机视角的 free。5.3 实操心得分享几个我踩坑总结出来的小技巧。第一排查内存问题前先sync一下把脏页刷盘这样 buff/cache 的统计会更准确。第二/proc/meminfo里的MemAvailable是内核估算的比 free 列靠谱但它在某些老内核上可能不存在需要留意内核版本。第三如果怀疑是内核 slab 问题可以尝试echo 2 /proc/sys/vm/drop_caches手动回收但这只是临时手段治标不治本而且生产环境慎用可能引起 IO 抖动。第四对于长期运行的服务建议定期用smem做一次内存快照建立基线。这样一旦出现异常有历史数据可以对比定位会快很多。第五容器环境下排查内存一定要区分宿主机视角和容器视角两者经常对不上容易误导判断。6. 从机制到工具建立自己的内存排查方法论6.1 分层排查的思路内存排查最忌讳东一榔头西一棒子。我习惯按“系统层 → 内核层 → 进程层 → 应用层”的顺序逐层往下。系统层看 free 和 /proc/meminfo内核层看 slab 和页表进程层看 smem 和 pmap应用层才去看具体代码和配置。这个顺序的好处是每一层都能排除掉一批可能性不会在无关方向上浪费时间。具体来说先看 available 判断是否真的紧张再看 shared 和 Shmem 排除共享内存接着看 Slab 和 PageTables 排除内核开销最后才落到进程和应用上。大部分“幽灵占用”问题在前三层就能定位到。6.2 工具选型建议工具不在多在于用对场景。free和/proc/meminfo是基础必看。smem比ps准确建议装上。slabtop查内核缓存pmap查进程内存映射lsof和fuser查文件引用这几个是排查共享内存和文件句柄的利器。如果要做长期监控建议把/proc/meminfo的关键字段采集起来画成趋势图。单点数据看不出问题趋势才能暴露泄漏和缓慢增长。我一般会重点监控MemAvailable、Shmem、Slab、PageTables这四个指标它们基本覆盖了大部分异常场景。6.3 预防胜于排查与其等出问题再排查不如提前做好预防。几个实用建议给关键服务配置内存上限避免单个服务吃光内存给/dev/shm设置合理大小并加监控定期巡检 slab 和共享内存占用容器环境配置好 cgroup 限制和对应的监控。还有一点很重要建立内存使用的基线。正常运行时 available 大概多少、slab 大概多少、shmem 大概多少心里要有数。有了基线任何偏离都能第一时间发现而不是等到告警了才手忙脚乱。我个人在实际操作中的体会是内存问题百分之八十的困惑都来自“统计口径不一致”。free 的口径、ps 的口径、cgroup 的口径各不相同混着看必然对不上。把每个工具的口径搞清楚再按分层思路去排查那些看似消失的内存其实都有迹可循。最后再分享一个小技巧遇到实在定位不了的情况cat /proc/meminfo从头到尾看一遍往往能在某个不起眼的字段里找到答案。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑