凌晨两点MySQL进程悄无声息地消失了业务告警响成一片。你登录服务器第一件事不是看日志而是先执行一条命令dmesg -T | grep -i killed process如果输出里躺着Out of memory: Kill process ... (mysqld)这种字样恭喜你你的CentOS 7服务器被Linux内核的OOM Killer盯上了。这篇博客就围绕OOM Killer的配置展开我会把CentOS 7环境下它的原理、内核参数、进程保护办法、完整配置流程以及排查技巧一次讲透适合被内存问题折腾过的运维、后端开发以及所有在自己服务器上跑数据库或Java应用的同学。先把话撂这儿OOM Killer不是bug它是内核在内存耗尽时最后的自保动作——从所有进程里挑一个最该杀的杀掉换回系统存活。既然要挑一个那怎么挑、怎么让自己重要的进程不被挑中、怎么调参数让内核在OOM时别直接panic这些才是真正值得花时间搞清楚的问题。1. 先搞明白OOM Killer它不是Bug是内核的最后一道保安1.1 OOM Killer是什么进程被杀的真相OOM Killer全称Out Of Memory Killer是Linux内核内存管理子系统里的一个自我保护机制。当系统物理内存和swap都用尽新的内存分配请求又无法满足时内核就面临两个选择要么整个系统卡死甚至崩溃要么从现有进程里挑一个牺牲品回收它的内存保证系统活下去。OOM Killer选的就是第二条路。它本质上是个丢车保帅策略但问题是丢车的标准由内核说了算如果不加干预完全按内核默认的评分规则来你辛苦跑着的数据库很可能就是那个被丢掉的车。一个很直观的类比航班超售了登机口工作人员必须劝退几位乘客否则整架飞机都走不了。OOM Killer就是那个工作人员谁的机票价值低就劝退谁。而我们的目标就是给自己重要的进程穿上金钟罩让内核在不得不劝退人的时候优先动那些无关紧要的进程。很多人有个误解以为OOM Killer只有在物理内存完全用尽时才触发。其实不然。Linux下有个内存超卖机制进程申请内存时内核通常不会立即检查物理内存够不够而是先给一张空头支票。等到进程真正往内存里写数据时内核才不得不兑现。这个机制是OOM Killer出现的根本土壤。1.2 内存超卖机制为什么物理内存没满进程也被杀Linux内核默认允许进程超额申请内存这个设计叫overcommit。进程调用malloc()申请1GB内存内核大概率直接说好给你哪怕物理内存只有512MB。为什么敢这么干因为绝大多数进程申请内存后并不会立刻全部用完。比如Java应用启动时按-Xmx2g申请堆空间实际可能只用了几百MB再比如fork()出的子进程通过写时复制Copy-on-Write与父进程共享物理页真正发生写入才分配独立内存。如果内核每次都严格按申请量预留物理内存系统能跑的进程数量会大幅缩水内存利用率也上不去。但空头支票开多了总有兑现不了的一天。当系统累计承诺的内存Committed_AS超过实际能提供的内存物理内存 swap再加上某个进程真的需要向内核要一块物理页时分配失败OOM Killer就会被触发。关键点来了这种触发跟物理内存还剩多少没有必然关系。有时候free -h显示还有几百MB可用OOM照样发生。因为所谓可用是缓存可以回收的量而内核承诺出去的内存总量已经超标。记住这个逻辑排查OOM时才不会走弯路。CentOS 7默认的内核版本是3.10.xOOM Killer机制在3.10里已经相当成熟和当前新版本内核的配置思路基本一致。你在网上搜到的很多老教程、老经验放到CentOS 7上仍然适用这点可以放心。2. 核心内核参数配置从 overcommit 到 panic_on_oom2.1 vm.overcommit_memory 三种模式的选型逻辑控制OOM Killer行为最核心的参数是vm.overcommit_memory它有三个取值对应三种内存超卖策略参数值模式说明适用场景0启发式内核根据进程内存映射类型估算是否允许超额默认值绝大多数通用服务器推荐保持1总是允许所有内存申请都放行不管物理内存够不够内存密集型计算集群极少使用2禁止超额超过CommitLimit的申请直接拒绝追求极致稳定的数据库、关键业务节点先看默认的0模式。内核会检查这次申请是不是危险的比如某个进程要映射一个特别大的私有可写区域且系统承诺内存已超限才会拒绝。这种启发式策略兼顾了内存使用率和系统稳定性大多数业务场景下表现都不错。我在CentOS 7上遇到的大部分OOM事件其实都是在0模式下发生的——因为默认允许超卖跑着跑着承诺内存耗尽才触发杀进程。再看2模式。这个模式要求系统严格遵守CommitLimit上限CommitLimit的计算公式是CommitLimit 物理内存 * vm.overcommit_ratio / 100 swap举个例子一台8GB物理内存、2GB swap的机器vm.overcommit_ratio取默认值50那么CommitLimit 8 * 50 / 100 2 6GB。也就是说系统所有进程累计承诺内存超过6GB后新的内存申请就会被拒绝。这种模式的好处是丑话说在前头——内存申请时直接报错进程要么处理异常要么退出总好过运行到一半被OOM Killer杀掉。坏处也很明显很多正常的内存弹性需求会被误伤比如Java应用启动时按最大堆申请内存即使实际用不到那么多也会被计入承诺内存。所以除非你有非常明确的内存上限控制需求否则不建议在通用服务器上开2模式。最后是1模式。开了它等于告诉内核任何申请都放行物理内存耗尽也用swapswap也耗尽就让OOM Killer处理。这种模式下OOM Killer触发得最频繁我基本只在跑内存型计算任务、且任务本身能扛得住被杀的场景里见过有人这么干。日常服务器不建议碰。2.2 vm.panic_on_oom 与 vm.oom_kill_allocating_task 的配合vm.panic_on_oom决定内存耗尽时内核是杀进程还是直接崩溃重启。取值说明如下0默认值触发OOM Killer杀进程系统继续运行。1内存耗尽时直接panic内核崩溃并触发重启。2比1更激进即使某些cgroup还能回收内存也直接panic。这个参数的价值在于如果业务无法接受进程被随机杀掉后服务降级运行宁可直接重启机器让一切归零那么可以设成1。但代价是整机停机时间更长而且可能丢失未落盘的数据。我个人的建议是除非业务有硬性要求否则保持0。vm.oom_kill_allocating_task参数则控制OOM发生时优先杀谁。默认值0表示内核根据评分选一个最该杀的进程设为1则直接杀掉当前触发内存分配的进程。这个参数在调试期很有用——如果某个已知进程总是因为自己分配内存太大而触发OOM设成1可以让元凶自食其果避免误伤别的进程。这两个参数的实际配合方式遵循一个原则能精确定位凶手就精确定位定位不了就把影响面控制住。比如你知道是某个Java应用的内存泄漏触发的OOM那可以给Java进程设置很高的oom_score_adj同时保持oom_kill_allocating_task0让内核评分时恰好选中它。这比直接设1要温柔得多因为内核会在触发OOM的线程上下文里做更多内存回收尽力挽救一下。上面这些参数修改方式分临时和永久两种。临时生效用sysctl -w重启后失效永久生效写进/etc/sysctl.conf然后执行sysctl -p加载。CentOS 7下我已经按这个套路操作过无数次稳定性很好。3. 保护关键进程oom_score、oom_score_adj 与 systemd 配置3.1 内核如何给进程打分oom_score 的计算逻辑理解了全局参数接下来重点来了——怎么让内核在OOM发生时尽量不杀你的关键进程。这就绕不开oom_score。每个进程在内核里都有一个评分范围是0到1000。分数越高的进程在OOM发生时越容易被选中。查看某个进程的评分方式很简单cat /proc/[pid]/oom_scoreoom_score的基准值主要取决于进程当前实际占用的内存包括RSS常驻内存、页表、swap使用量等占系统总内存的比例越高分数越高。然后内核会做一些调整运行时间长的进程分数会适当降低理由是老进程对系统贡献大且重启成本高有root权限的进程在早年内核版本里分数会有折扣直接访问硬件设备的进程比如显卡驱动相关进程分数也会被调低。重点来了内核提供了两个用户可控的调整接口老的oom_adj和新的oom_score_adj。CentOS 7上你仍然能设置oom_adj但那是历史遗留接口取值范围-17到15映射关系复杂新配置一律用oom_score_adj。它的取值范围是-1000到1000规则很直白正数增大被选中概率值越大优先级越高。负数降低被选中概率。-1000直接退出OOM候选名单也就是给进程上了免死金牌。每个进程的最终oom_score大致等于基础分加上oom_score_adj的偏移。比如某个进程的基础分是300oom_score_adj设为-500那么最终评分就是负数内核几乎不会选中它。判断一台机器上OOM发生时最可能先杀谁可以跑一个循环脚本把所有进程的评分捞出来for pid in /proc/[0-9]*; do p$(basename $pid) score$(cat $pid/oom_score 2/dev/null) comm$(cat $pid/comm 2/dev/null) echo PID$p SCORE$score COMM$comm done | sort -t -k3 -rn | head -20这个脚本我在排查OOM倾向时几乎必用。排在前面的进程就是下次OOM的高危候选人。你一眼就能看到平时最占内存的进程是不是排在第一位如果不是那就要想想它有没有被别的因素加分。3.2 systemd 与手动方式配置 OOMScoreAdjust 实操在CentOS 7上绝大多数服务都由systemd托管配置进程的oom_score_adj最正规的办法是修改unit文件。以mysqld为例[Service] OOMScoreAdjust-1000设置成-1000意味着MySQL进程在OOM评分里直接出局内核杀谁都不会动它。保存后执行systemctl daemon-reload systemctl restart mysqld验证配置是否生效可以获取进程PID后查评分systemctl show mysqld.service -p OOMScoreAdjust cat /proc/$(pgrep -x mysqld)/oom_score_adj两个命令的输出都应该是-1000。更多时候我们不建议直接把OOMScoreAdjust拉到-1000这种极端值而是给个-500或-800让进程很难被杀但不是绝对不杀。背后的逻辑后面5.2节细说这里先记住-1000是金牌-500是银牌。对于不是systemd托管的进程可以手动写入echo -500 /proc/[pid]/oom_score_adj但这种方式是临时的进程重启后失效。生产环境还是建议尽可能用systemd托管或者写个守护脚本在进程启动后自动设置。CentOS 7下我还遇到过一种情况服务明明设置好了OOMScoreAdjust但重启后评分没生效。排查下来发现是进程fork了子进程而真正干活的子进程用的是另一套评分。所以设置完别只盯着主进程看重点检查实际承担业务负载的工作进程。3.3 别忘了 cgroup 这一层容器场景的 OOM 控制CentOS 7默认支持cgroup v1systemd和容器运行时都基于cgroup做资源隔离。OOM Killer不仅存在于全局内存层面也存在cgroup层面。每个cgroup目录下有一个memory.oom_control文件查看方式cat /sys/fs/cgroup/memory/memory.oom_control输出里有几个字段oom_kill_disable为0表示允许这个cgroup内的OOM Killer杀进程为1表示关闭under_oom为1表示当前cgroup处于内存不足状态oom_kill是累计被杀的进程次数。如果你用Docker跑容器早期版本里--oom-kill-disable参数可以关闭容器内的OOM Killer。关闭之后容器内存达到上限时内核不会杀进程而是阻塞该cgroup内所有进程的内存申请表现出来就是容器假死、卡住不动机器整体倒没事。这里有个非常关键的优先级关系cgroup的OOM判定先于全局OOM。如果一个容器设置了内存上限比如memory.limit_in_bytes1G那么容器内进程实际内存占用达到1G时cgroup OOM就会触发根本轮不到全局OOM Killer。所以排查容器应用被杀时先看cgroup层级的memory.oom_control别一上来就查全局日志。我处理过的不少容器内MySQL被杀事件真凶都在cgroup限制上全局OOM Killer根本没参与。4. 落地一套 CentOS7 OOM Killer 配置方案可直接抄4.1 通用服务器推荐参数与 sysctl 配置步骤先说结论如果你的服务器不是特别明确的数据库节点或内存敏感业务请优先保持默认配置。CentOS 7默认的overcommit_memory0、panic_on_oom0已经是最平衡的赛跑状态强行改成别的模式往往捡了芝麻丢西瓜。真正需要调整的是对关键进程的oom_score_adj而不是全局参数。所以通用服务器的落地动作集中在确认内核参数的当前值以及补充sysctl配置里的合理项。配置步骤如下首先查看现状sysctl vm.overcommit_memory vm.panic_on_oom vm.oom_kill_allocating_task然后编辑/etc/sysctl.conf追加以下内容注意如果你确认需要改成别的模式按2.1节规则修改不要照抄这些值vm.overcommit_memory 0 vm.panic_on_oom 0 vm.oom_kill_allocating_task 0 vm.min_free_kbytes 65536最后一个vm.min_free_kbytes很多教程会忽略。它的作用是给内存分配预留一块应急储备金保证内核在内存紧张时仍能完成关键的分配动作不至于立刻陷入死锁。默认值通常偏小机器内存超过8GB时建议手动设到64MB以上。我自己在16GB内存的机器上设过128MB效果良好没有发现可用内存明显缩水。改完执行sysctl -p sysctl vm.overcommit_memory vm.panic_on_oom vm.oom_kill_allocating_task vm.min_free_kbytes确认输出和配置文件一致即可。这里解释一下为什么不建议通用服务器把overcommit_memory改成2。很多刚接触OOM Killer的运维同学看到禁止超额这几个字就觉得很安全直接设成2结果第二天业务就报内存分配失败。原因很简单像Redis、Java这类应用经常申请远超实际使用量的内存2模式会把这些申请全部打回。CentOS 7默认的0模式恰恰能容忍这种浪费,系统反而跑得更好。4.2 数据库、Java 等关键进程的单独保护配置接下来是重头戏如何给关键进程配置保护。以最常见的数据库场景为例。如果MySQL由systemd托管直接创建override文件systemctl edit mysqld.service写入[Service] OOMScoreAdjust-1000保存后生效。这样做的好处不仅是进程重启后自动生效而且不用修改原始unit文件升级软件包时不会被覆盖。如果是Java应用比如一个跑在Tomcat里的Spring Boot服务我一般给到-500而不是-1000。原因后面细说这里先记住Java的内存模型比数据库复杂得多完全豁免会让它成为内存黑洞最终拖垮整台机器。对于没有systemd托管的自研进程写个启动脚本在启动后设置评分#!/bin/bash nohup /opt/myapp/bin/startup.sh pid$! echo -800 /proc/$pid/oom_score_adj注意一个问题Java、Node这类进程启动后可能fork出worker子进程子进程会继承父进程的oom_score_adj这个没问题但如果子进程自己重新设置了评分那就要逐个检查。下面给出一个验证脚本批量检查系统中的关键进程for proc in mysqld java nginx redis-server; do pid$(pgrep -x $proc | head -1) if [ -n $pid ]; then echo $proc PID$pid adj$(cat /proc/$pid/oom_score_adj 2/dev/null) fi done除了oom_score_adjJava应用还有个特别重要的点**-Xmx参数设置一定要为堆外内存留足余量**。JVM的堆外内存包括Metaspace、线程栈、直接缓冲区Direct ByteBuffer等这部分不受-Xmx控制。我一个真实案例是-Xmx2g应用实际占用的物理内存却超过3GB。设置-Xmx时给系统总内存留出至少1/4的空闲空间再配合OOMScoreAdjust-500Java应用基本能安稳运行。至于swap要不要配我的态度很明确CentOS 7服务器一定要配swap容量建议等于物理内存的50%到100%但不能指望它救一切。swap太小OOM概率大增swap太大内核会把大量冷数据换到磁盘IO负载飙升系统整体响应变慢。常见误区是看到OOM了就把swap加几个G治标不治本真正的问题还是内存占用失控。5. 排查实战与避坑现场还原与六个操作禁忌5.1 从 dmesg 和 messages 日志还原 OOM 现场配置做得再好真出事的时候还得看日志。OOM Killer发生时会往内核环形缓冲区写一条详细记录最直接的查看命令dmesg -T | grep -i out of memory\|killed process | tail -50输出大致长这样[Wed Feb 14 02:13:45 2026] Out of memory: Kill process 12765 (mysqld) score 920 or sacrifice child [Wed Feb 14 02:13:45 2026] Killed process 12765 (mysqld) total-vm:5238784kB, anon-rss:1832424kB, file-rss:34512kB, shmem-rss:0kB信息量很大逐个字段拆解score 920这个进程的OOM评分920属于非常高的分数基本是当时系统里内存占用最猛的进程。total-vm 5238784kB进程虚拟内存总量约5GB。anon-rss 1832424kB匿名页常驻内存约1.75GB这部分是进程真正在使用的物理内存不含文件缓存。file-rss 34512kB文件映射页比如代码段和映射文件。拿到这条日志后不要急着处理单个进程先回答三个问题系统总内存和swap当时是什么状态用sar -r回看历史趋势确认是突然飙升还是逐步耗尽。被杀的进程是不是当前最占内存的进程如果还有分数更高但没被杀的进程说明评分规则里有别的因素在影响。日志里有没有其他异常比如内核报的某个驱动内存泄漏、大量page allocation failure。除了dmesgCentOS 7的/var/log/messages也会记录OOM相关日志。我习惯两个都查dmesg偏内核原始messages会带上syslog的额外时间戳方便与业务告警对齐时间线。还有一个有用的辅助手段开启内存使用审计。虽然没有必要常开但对于反复触发OOM的服务器可以写一个定时任务采集free -h和 top内存前五的进程保留历史基线。这样下次OOM时你能对比出到底是谁的成长曲线不对劲。5.2 六个高频误区与实测经验误区一给所有重要进程都设置 -1000这条必须放在最前面。把所有关键进程全部拉出OOM名单看似安全实际危险。当内存真的耗尽时内核只能从剩下的进程里选而这些进程往往是不那么重要的缓存、日志、监控组件——它们被杀后重要进程的内存压力并没有减轻新一轮申请还会继续系统进入频繁杀进程的恶性循环最终可能panic_on_oom都救不了你。正确做法是数据库这类重启一次代价极大的进程给-1000普通应用给-500中间件给-200或0形成梯度。保护少数放弃多数才是OOM Killer配置的精髓。误区二把 panic_on_oom1 当成关闭OOM Killer有些教程说设置panic_on_oom1可以防止进程被杀这话害人。设成1之后OOM发生时内核直接panic整机重启业务中断时间比杀一个进程长不知道多少倍。我见过有人为了不让重要进程被杀把数据库服务器的panic_on_oom设为1结果每次OOM整机重启数据库数据损坏风险剧增。panic_on_oom只在宁可重启也不降级运行的场景下才值得考虑。误区三修改完内核参数不验证sysctl -p命令执行成功后不代表所有参数都符合预期。有些参数受内核版本影响同一个值在不同版本里行为可能有差异。我踩过这样的坑在/etc/sysctl.conf里写了vm.overcommit_memory2但忘记sysctl -p,最后服务器重启时参数才加载中间那几周系统一直跑在旧参数下。养成习惯改完立即sysctl vm.overcommit_memory确认当前运行值。误区四swap设置不当swap设0物理内存一紧张立刻OOMswap设太大系统IO会持续走高。均衡方案是物理内存小于8GB的机器swap设为物理内存的100%内存大于8GB的服务器swap设为4到8GB就够。同时设置vm.swappiness10让内核尽可能少用swap只在内存真的紧张时才启用。这个值CentOS 7默认是30调低对延迟敏感的服务有明显改善。误区五只改配置不查内存泄漏OOM Killer配置得再完美也只是一个止血动作。如果业务进程本身存在内存泄漏你设置-1000也好设置-500也罢只是延缓了被杀的时间真正的问题依旧在。我建议每次OOM事件处理完后花时间用valgrind、jemallocprofile工具或者JVM的Heap Dump定位泄漏点而不是把配置改完就当解决问题。从根上防止OOM比任何参数都靠谱。误区六忽略cgroup层级的OOM控制在容器环境里全局OOM Killer往往不是第一个动手的。容器平台通过cgroup给每个容器设定内存上限容器内内存用尽时cgroup层级的OOM会先杀掉容器里的进程。所以配置了全局oom_score_adj之后还要检查容器运行参数里的内存Limit有没有给够memory.oom_control里oom_kill_disable的状态是什么。两层一起看才能把进程被杀的真相看清楚。把这些经验沉淀下来你会发现在CentOS 7上配置OOM Killer并不复杂关键是想清楚三个问题全局参数要不要动、关键进程怎么保护、出事后怎么快速定位。我在这一块的真实体会是OOM Killer不是用来配置好了就高枕无忧的它是系统给你的一道最后防线真正的功夫永远在日常的内存监控和容量规划上。最后分享一个我自己的落地顺序先看日志定位真实触发原因再评估内存使用趋势最后根据业务重要性设置oom_score_adj梯度。每一步都验证过再往下一步走踩坑的概率会小很多。希望这篇内容能帮你少熬几个凌晨两点的夜。