恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
内存爆了磁盘却还有空间?拆透Linux内存与Swap自救机制
首页
资讯中心
/
内存爆了磁盘却还有空间?拆透Linux内存与Swap自救机制
内存爆了磁盘却还有空间?拆透Linux内存与Swap自救机制
发布时间:2026/10/13 2:35:43
如果监控页面上同时跳出“内存使用率100%”和“磁盘剩余几十G”而旁边守着你的同事来了一句“磁盘还有这么多扩下内存不就行了吗”你会怎么接我在运维现场见过太多人栽在这句话上。磁盘有空间跟内存能不能用上中间隔着好几个层面的机制。今天不绕弯子直接把内存和磁盘这两件事拆透它们各自的角色、Linux在内存耗尽时的自救机制、怎么快速定位是谁吃光了内存以及到底有哪些真正有效的优化策略。这篇文章适合刚接触运维的初学者也适合那些虽然会用top但一直没搞懂buff/cache和available区别的朋友。1. 先界定角色内存是工作台磁盘是仓库1.1 内存的本质快、贵、断电即失内存RAM是计算机的“工作台”。你的程序在运行的时候指令和正在操作的数据都必须在内存里放着CPU 直接跟内存打交道。工作台的特点是离手边近、拿取快、但台面就那么大。内存的访问延迟通常在几十纳秒级别带宽高得惊人一根普通 DDR4 内存条的读写带宽就能到几十 GB/s。但内存很贵而且它有个致命弱点——掉电数据全没。所以内存只能用来放“正在被使用”的东西程序崩溃了、机器重启了内存里的数据统统蒸发。这也解释了另一个现象进程哪怕只是打开一个文件操作系统也要先把文件内容从磁盘读进内存也就是 page cache 机制然后 CPU 才能去处理。换句话说磁盘是“原料仓库”内存是“加工台”CPU 是“工人”。工人干活必须从加工台上拿料没法直接去仓库里抓。1.2 磁盘的本质大、慢、断电不丢磁盘是“仓库”。仓库的特点是容量大、东西放得久、但取货慢。机械硬盘的寻道加延迟在毫秒级别比内存慢了四五个数量级即便是现在常用的 NVMe SSD延迟依然在微秒级跟内存还是隔着两三个数量级。差距有多大内存读一次数据需要 80 纳秒的话SSD 读一次数据可能需要 80 微秒相差大约 1000 倍。所以磁盘是用来放“暂时不用但以后还要用”的东西操作系统镜像、应用程序文件、数据库数据文件、日志统统存磁盘。断电了、重启了这些东西都还在。但你不可能让 CPU 直接从磁盘上跑一个程序、也没法让正在运行的进程把它的变量直接放在磁盘上——那样整个系统会慢到怀疑人生。1.3 两个指标为什么可以“南辕北辙”搞清楚了角色定位再回头看“内存爆了但磁盘还剩几十G”就很容易理解了内存和磁盘是两个独立的资源池它们之间有联系数据要在两者之间搬运但并不是互相替换的关系。磁盘剩余空间再多也无法直接变成进程能用的内存地址空间。如果进程需要的内存超过了物理内存操作系统有两个选择一是把一部分暂时用不到的内存页“挪”到磁盘上腾出物理内存给活跃的进程——这就是 swap后面细讲二是干脆杀死某个进程来释放内存——这就是 OOM Killer。但无论哪种都不是“把磁盘空间分一点给内存用”这么简单因为 swap 再怎么说也是磁盘速度跟真正的内存不是一个量级。这里有一个新手特别容易混淆的点free -h显示磁盘剩余几十 G跟内存能不能用是两回事。你看到磁盘剩余大只能说明仓库还有地方放东西并不能说明工作台上还有空位子。2. Linux 的内存耗尽自救机制Swap 与 OOM Killer2.1 Swap 的工作方式与真实代价Swap 的本质是操作系统拿磁盘上的一块区域可以是分区也可以是文件当作内存的“后备仓库”。当物理内存不够用的时候内核会挑一些“不太活跃”的内存页比如某个进程很久没访问的变量、或者已经读取过但暂时不用的文件缓存页写入 swap 区把物理内存腾出来给正在活跃的进程用。等这些数据被再次访问时再从 swap 换回内存。这个过程叫换页swapping/paging。听起来很美但代价就是慢。一个运行中的进程访问它自己的内存页如果发现这个页已经被换到 swap 上了就需要先等磁盘读回数据——这个等待时间可能就是几毫秒到几十毫秒比正常内存访问慢了几万倍。如果一个系统频繁地在内存和 swap 之间搬运数据CPU 大部分时间都花在等待 I/O 上表现就是 load average 飙升、命令响应迟钝俗称“swap 抖动”。这里必须说清楚swap 不是用来“解决内存不足”的它只是“延迟崩溃”的缓冲带。配置了 swap 的系统在内存吃紧时还能多撑一会儿、给你留出排查和介入的时间完全没有 swap 的系统内存一紧张就直接触发 OOM连“抢救”的余地都没有。所以生产环境一般建议配置 swap但不要指望靠它跑业务。2.2 OOM Killer内核的最后一道裁决当物理内存和 swap 都用尽了内核必须做出一个残忍的决定杀进程。OOM Killer 会根据一套评分机制给所有进程打分oom_score分数越高的进程越可能被选中干掉。评分主要看两个维度进程占用的内存量、以及你对它设置的oom_score_adj调整值。理论上占内存越大的进程分数越高越容易被杀——这其实是为了用“最小代价”换回最大内存。但问题是占内存大的往往就是你正在跑的核心服务。所以很多运维会在关键服务上设置oom_score_adj为负数降低它被选中的概率反过来对某些不重要的批量任务设置正数让它们成为优先被牺牲的对象。OOM 触发后内核会在系统日志里记录常见的报错是类似“Out of memory: Kill process 12345 (java) score 892 or sacrifice child”这样的信息。判断一个进程是不是被 OOM 杀死的看dmesg或journalctl -k就能找到线索。一个常见的误区是进程突然不见了大家第一反应是去看应用日志但应用日志里往往干干净净真正的死因在系统内核日志里。2.3 动手验证压力测试下系统如何一步步“垮掉”只看理论不够直观我建议你在测试环境做一次小实验。准备一台内存 2G 的虚拟机写一段简单的分配内存的脚本比如用 Python 的列表不断追加数据或者用dd从/dev/zero读数据填充内存观察整个过程中free -h的变化和系统响应。实测下来的过程大致是这样最初可用内存快速下降buff/cache先被压缩接着内存耗尽swap 使用量开始上涨同时 CPU 的waI/O 等待比例明显升高继续加压swap 占满此时系统开始卡顿top命令都可能半天才刷新一次最后内核出手OOM Killer 把压测进程杀掉命令行回到 shellswap 里的数据慢慢换回内存系统恢复。整个过程就是把原理变成“肉眼可见”的最好方式。我强烈建议运维新人自己跑一次这个实验。只有亲眼看过系统从正常到卡顿再到 OOM 杀进程的全过程你才会真正理解 swap 抖动是什么感觉、OOM 日志长什么样、为什么不能等到内存爆了才去查问题。3. 定位内存大户从现象到根因的排查路径3.1free命令的正确打开方式排查内存问题第一站通常是free -h。新手很容易盯着used那一列看哇已经用了 90%但真正的关键字段是available。available表示在不触发 swap 的前提下还能分配给新进程的内存估算值。它跟free的区别在于free只是完全空闲的物理内存而available还会把可回收的buff/cache也算进去。这就涉及另一个高频疑惑为什么used很高系统却一点不卡因为 Linux 会把空闲内存尽量用来做 page cache缓存磁盘文件内容这是内核的“内存闲着也是闲着不如拿来加速读”的策略。这部分内存是可以被内核随时回收的所以它不该算作“不可用的内存”。判断系统内存紧不紧张优先看available和 swap 使用量的变化趋势而不是单纯看used百分比。顺带提一句free命令在旧版本里输出的是-/ buffers/cache这样两行新版本则直接列出buff/cache和available。如果你在老的发行版上看到这两行知道它表示的是相同含义即可。3.2 从进程维度看内存占用系统整体内存高最终一定要落到“哪个进程在吃内存”这个问题上。几个常用手段top进入后按大写M键按内存排序直接看到内存占用最高的进程列表。ps aux --sort-%mem按内存占用率排序配合head取前 N 行适合快速截图留存。pmap -x pid查看某个进程的详细内存映射能看到每一段内存分布在哪里、大小多少。cat /proc/pid/status里VmRSS是进程实际驻留在物理内存的大小RSSVmSize是虚拟地址空间大小两者差距可能很大分析时以 RSS 为准。这里面最容易误导人的是 VIRT虚拟内存列。某些进程比如 Java 虚拟机、数据库的 VIRT 值动辄几十 G但那只是它向操作系统申请的虚拟地址空间并不代表真的占用了这么多物理内存。判断一个进程真实吃了多少内存请以 RSSps里的RSS列、top里的RES列为参考。看到 VIRT 巨大就先别慌先确认 RSS。3.3 案例复盘一次疑似内存泄漏的定位说一个我实际处理过的类似场景某公司的后端服务每隔几天就“神秘失踪”重启后又能撑几天。第一次排查时大家都在看应用日志没发现异常怀疑是运维误操作。后来在一次故障重现时我守在现场第一时间查了系统日志果然看到 OOM Killer 的记录被杀的就是那个 Java 服务。接着用ps aux观察这个进程的 RSS发现它在几天内从 1G 一路涨到 5G而且完全没有回落的迹象——这是典型的持续增长趋势单靠“业务高峰期内存波动”解释不了。再用pmap看内存主要堆在堆内存区域。结合 Java 应用的特点基本可以判断是进程内缓存或集合对象只增不减。后来开发人员在代码里定位到一个全局缓存 Map只往里放数据、没有过期清理机制修掉之后内存曲线就平了。这个过程可以提炼成一套通用排查路径先看系统日志确认是否 OOM → 再看进程 RSS 随时间的变化是否单向增长 → 用pmap或堆内存分析工具定位内存集中在哪 → 结合应用代码找根因 → 修复后持续观察。这套路径对 Linux 上的大多数服务都适用不管你是跑 Java、Go 还是 Python。4. 优化策略让有限的内存扛住更多业务4.1 应用层“节流”先别急着加机器内存优化的第一优先级永远是应用层因为同样的业务代码内存在不同写法下的消耗可能差好几倍。几个常见且有效的方向调整运行时的堆内存参数。Java 应用要合理设置-Xmx不要图省事直接给满JVM 的堆外内存、Metaspace 也要预留。Python 或 Node 应用则要关注全局缓存和线程栈的默认大小。数据库类服务的连接池、缓存大小、排序缓冲区每一项都是内存消耗大户。比如 MySQL 的innodb_buffer_pool_size通常建议设置为可用内存的 60%~70%设大了会挤占其他服务设小了命中率低、磁盘 I/O 升高。日志库、ORM 框架的批量处理机制能用流式就别一次性全量加载。一个典型的反面案例是用read()一次性读入一个大文件的全部内容到内存文件 2G 就直接把内存打爆。正确的姿势是分块读取、流式处理。多实例部署时要算清楚“每实例内存 × 实例数”的总账。云服务器上最常见的内存爆掉是因为 Java 服务写了-Xmx4g然后同一台机器上启动了三个实例加上系统和其他进程总需求远超物理内存。4.2 系统层“开闸”内核参数的微调空间应用层优化之后系统层还有几个旋钮可以碰但每动一个都要有充分的监测数据支撑。vm.swappiness控制内核“有多倾向于把内存页换到 swap”。取值 0~100默认通常是 60。数值越大内核越积极使用 swap数值越小越倾向于优先释放 page cache。很多老教程喜欢让你改成 0理由是“少用 swap 能提升性能”。但实测下来对现代服务器设 0 意味着系统宁可大量回收 page cache 也不换出进程内存页有时候反而会导致文件读缓存命中率下降、磁盘 I/O 上升。我自己的做法是普通业务服务器设 10 左右数据库服务器可以在充分测试后设 1~10 之间的值避免极端激进。这里没有放之四海皆准的参数核心是观察设值前后的pswpin/pswpout是否有明显变化。vm.overcommit_memory控制内核是否允许进程申请超过物理内存加 swap 的虚拟地址空间。默认值 0 表示启发式检查内核会根据当前空闲内存情况决定是否放行。某些应用比如 Redis 的fork保存、大内存计算框架会申请大量虚拟内存这时候可能需要改成 1总是允许 overcommit但要非常谨慎因为一旦允许超卖系统就更依赖 OOM Killer 来处理问题。sysctl -w vm.drop_caches可以直接回收 page cache注意它只回收干净的缓存页也就是磁盘内容已经在内存里、且没有被修改过的页脏数据页不会丢。但我建议只在测试环境或者明确需要压测的场景用echo 3 /proc/sys/vm/drop_caches这种操作生产环境让它自己回收就好手动清缓存一时看着内存“变多了”实际意义有限反而可能让接下来的读写重新经历缓存预热。4.3 Swap 与内核参数的取舍建议swap 到底要不要配配多大这是运维群里每月必吵的话题。我的建议很务实服务器内存小4G 以下且跑的业务重要配置 swap大小可以设为物理内存的 1~2 倍。关键时刻能多撑一阵子给你保留 SSH 排查的机会。服务器内存较大16G 以上且跑的是内存型应用比如 Redis、大数据计算swap 可以配置但不要设太大建议 4G~8G 即可。原因在于当内存型应用开始大量使用 swap 时性能已经崩到不可用了大 swap 只是让你的故障持续更久反而不如早点 OOM、早点恢复。数据库服务器swap 建议配置但数值不要大同时配合swappiness设置较低值尽量避免数据库进程的内存页被换出。另外要注意 swap 文件的形式在当前环境下也很常见fallocate -l 4G /swapfile mkswap /swapfile swapon /swapfile就能快速建一个 4G 的 swap 文件不依赖额外的分区适合云服务器。生产上记得把这条配置写进/etc/fstab否则重启后失效。5. 常见问题与排查技巧实录5.1 高频故障速查对照表现象可能原因快速判据处理动作free显示 used 高但系统流畅page cache 占用了大部分内存available余量充足、swap 为 0无需处理属正常策略系统卡顿、load 升高内存紧张触发 swap 抖动si/so列持续有换页、swap used 上涨定位内存大户评估扩容或应用优化进程无日志却突然消失被 OOM Killer 杀掉了dmesg有 Kill 记录检查应用内存增长趋势调整oom_score_adj进程 RSS 持续上涨不回落内存泄漏或缓存无上限连续观察 RSS 曲线用pmap或堆分析工具定位修复应用top里 VIRT 大得吓人虚拟地址空间大但不代表实际占用RSS/RES 其实不大不用处理关注 RSS 即可这张表是我日常排查的第一张“地图”。大部分内存相关问题沿着表格里的方向和工具走一遍基本都能定位到六七成。5.2 实操中容易踩的坑第一个坑把buff/cache当成“不可用内存”。有些监控平台直接取used的百分比做告警或者说used total - free没扣掉 cache导致内存明明很健康却一直在报警。正确做法是告警指标用available或者至少用used - buff/cache来估算真实占用。第二个坑设置了swappiness0就以为永远不会用到 swap。实际上在极端内存压力下内核仍然可能换出内存页该 OOM 还是会 OOM。swappiness只是调整“倾向”不是“开关”千万别把它当成免死金牌。第三个坑只盯整机内存、忽略单进程限制。真实生产中一个节点的内存问题常常是某个进程把内存吃满了影响同机的其他进程。除了整机告警一定要给关键进程设置 ulimit 或使用 systemd 的MemoryLimit、cgroup 限制。尤其是那些第三方闭源组件你不知道它什么时候会出幺蛾子提前“圈起来”最稳妥。第四个坑拿到 OOM 日志以为是内存不够立刻加物理内存。有一类问题其实是进程申请了超出地址空间限制的内存比如 32 位程序天然到不了 4G 以上加再多物理内存也没用。判断方法很简单看看被杀的进程是 64 位还是 32 位以及它在崩溃前VmSize是否逼近了某个固定上限。5.3 一个值得养成的习惯趋势监控优于事后救火排查了这么多次内存问题我越来越觉得内存问题的核心不是“爆了怎么办”而是“怎么在爆之前发现趋势”。RSS 单向增长、swap 使用率缓慢爬升、available 持续走低——这些趋势指标比单次快照有价值得多。哪怕是简单写个脚本把free -h和关键进程的 RSS 每一分钟记录一次配合一个最朴素的折线图都能让你的排查效率提升一大截。我自己在管理服务器时固定的动作是每台机器落地一个采集脚本把MemAvailable、swap used、Top 10 进程的 RSS 打到监控系统里告警阈值不只看瞬时值还看“连续 N 分钟持续下降”。这样做的好处是能在故障发生前就收到预警而不是等到 OOM 杀完进程才从睡梦中被叫醒。最后再分享一个细节排查内存问题时一定要把dmesg -T这个命令用起来。-T参数能把内核日志的时间转换成可读格式方便你对照应用日志的时间轴确定 OOM 到底是发生在应用异常之前还是之后。这个先后顺序决定了你是该改代码、调参数还是干脆扩容——顺序判断错了后面全白做。