linux OOM-killer机制(杀掉进程,释放内存)

时间:2022-01-11 20:58:58

Linux下面有个特性叫OOM killer(Out Of Memory killer),这个东西会在系统内存耗尽的情况下跳出来,选择性的干掉一些进程以求释放一些内存。相信广大从事Linux服务端编程的农民工兄弟们或多或少遇到过(人在江湖漂,哪有不挨刀啊)。典型的情况是:某天机器突然登不上了,能ping通,但是ssh死活连不了。原因是sshd进程被OOM killer干掉了(泪流满面)。重启机器后查看系统日志会发现血淋淋的Out of Memory: Killed process ×××、Out of Memory: Killed process 〇〇〇。一篇狼藉,惨不忍睹。

前段时间我就被OOM killer虐过一次。情况是这样的。一个简单的分布式系统,A,B,C等对称的节点分担处理X节点吐出的数据,然后将结果塞到Y节点去。结果负荷一大,ABC处理不过来,数据堆积在内存中,内存耗光,逼得OOM killer出来收拾局面,sshd被干掉,更杯具的是,重启后发现,syslogd也被干掉了,而且是在sshd前面被干掉。想查日志也只能查到一部分。血淋淋的。这也引出了分布式系统设计中的问题:想上面那种设计,要防止ABC中某个节点突然挂掉,然后导致其他节点负荷突然增加紧跟着也挂掉(连锁挂掉)。

回归正题,Linux下面的OOM killer到底是什么样一个机制呢,它在什么时候会跳出来,又会选择那些进程下手呢。

什么时候跳出来

先看第一个问题,它什么时候会跳出来。是不是malloc返回NULL的时候跳出来呢?不是的,malloc的manpage里有下面一段话:

By default, Linux follows an  optimistic  memory  allocation  strategy. 
This  means  that  when malloc() returns non-NULL there is no guarantee 
that the memory really is available.  This is a  really  bad  bug.   In 
case  it  turns  out that the system is out of memory, one or more processes 
will be killed by the infamous OOM killer.   In  case  Linux  is 
employed  under  circumstances where it would be less desirable to suddenly 
lose some randomly picked processes, and moreover the kernel version  
is  sufficiently  recent,  one can switch off this overcommitting 
behavior using a command like:

# echo 2 > /proc/sys/vm/overcommit_memory

上面一段话告诉我们,Linux中malloc返回非空指针,并不一定意味着指向的内存就是可用的,Linux下允许程序申请比系统可用内存更多的内存,这个特性叫Overcommit。这样做是出于优化系统考虑,因为不是所有的程序申请了内存就立刻使用的,当你使用的时候说不定系统已经回收了一些资源了。不幸的是,当你用到这个Overcommit给你的内存的时候,系统还没有资源的话,OOM killer就跳出来了。

Linux下有3种Overcommit的策略(参考内核文档:vm/overcommit-accounting),可以在/proc/sys/vm/overcommit_memory配置。取0,1和2三个值,默认是0。

0:启发式策略,比较严重的Overcommit将不能得逞,比如你突然申请了128TB的内存。而轻微的Overcommit将被允许。另外,root能Overcommit的值比普通用户要稍微多些。

1:永远允许Overcommit,这种策略适合那些不能承受内存分配失败的应用,比如某些科学计算应用。

2:永远禁止Overcommit,在这个情况下,系统所能分配的内存不会超过swap+RAM*系数(/proc/sys/vm/overcmmit_ratio,默认50%,你可以调整),如果这么多资源已经用光,那么后面任何尝试申请内存的行为都会返回错误,这通常意味着此时没法运行任何新程序。

补充(待考证):在这篇文章:Memory overcommit in Linux中,作者提到,实际上启发策略只有在启用了SMACK或者SELinux模块时才会起作用,其他情况下等于永远允许策略。

跳出来之后选择进程的策略

好了,只要存在Overcommit,就可能会有OOM killer跳出来。那么OOM killer跳出来之后选目标的策略又是什么呢?我们期望的是:没用的且耗内存多的程序被枪。

Linux下这个选择策略也一直在不断的演化。作为用户,我们可以通过设置一些值来影响OOM killer做出决策。Linux下每个进程都有个OOM权重,在/proc/<pid>/oom_adj里面,取值是-17到+15,取值越高,越容易被干掉。

最终OOM killer是通过/proc/<pid>/oom_score这个值来决定哪个进程被干掉的。这个值是系统综合进程的内存消耗量、CPU时间(utime + stime)、存活时间(uptime - start time)和oom_adj计算出的,消耗内存越多分越高,存活时间越长分越低。总之,总的策略是:损失最少的工作,释放最大的内存同时不伤及无辜的用了很大内存的进程,并且杀掉的进程数尽量少。

另外,Linux在计算进程的内存消耗的时候,会将子进程所耗内存的一半同时算到父进程中。这样,那些子进程比较多的进程就要小心了。

当然还有其他的策略,大家可以参考文章:Taming the OOM killerWhen Linux Runs Out of Memory