注: 本文为 “Linux OOM” 相关文章合辑。
Linux OOM 终结者
译者:花名有孚
| 2015-07-21 08:47
现在是早晨 6 点钟。已经醒来的我正在总结到底是什么事情使得我的起床闹铃提前了这么多。我们的监控系统显示,Plumbr 服务出故障了。
现在我可以开始处理故障了。首先要怀疑的是应用程序本身,因为它在崩溃之前一点异常也没有。应用程序日志中没有错误,没有警告,也没有任何可疑的信息。
我们部署的监控系统发现进程已经挂掉了并重启了服务。30 分钟后,我在 /var/log/kern.log
日志中发现了下面的信息:
Jun 4 07:41:59 plumbr kernel: [70667120.897649] Out of memory: Kill process 29957 (java) score 366 or sacrifice child
Jun 4 07:41:59 plumbr kernel: [70667120.897701] Killed process 29957 (java) total-vm:2532680kB, anon-rss:1416508kB, filers:0kB
很明显我们被 Linux 内核给坑了。Linux 里面许多守护进程是由几个内核作业所看管的,其中的一个犹为恶毒。所有的现代 Linux 内核中都会有一个内存不足终结者(Out of memory Killer, OOM Killer)的内建机制,在内存过低的情况下,它会杀掉你的进程。当探测到这一情况时,这个终结者会被激活,然后挑选出一个进程去终结掉。选择目标进程使用的是一套启发式算法,它会计算所有进程的分数,然后选出那个分数最低的进程。
理解 OOM Killer
默认情况下,Linux 内核会允许进程请求的内存超出实际可用内存的大小。这在现实世界中是有意义的,因为大多数进程其实并不会用到所有分配给它的内存(注:同一时间内不会全用到)。和这个问题最类似的就是运营商了。他们承诺卖给用户的都是 100Mb 的带宽,这实际上远远超出了他们的网络容量。他们赌的就是用户实际上并不会同时用完分配给他们的下载上限。一个 10Gb 的连接可以很轻松地承载 100 个以上的用户,这里的 100 是通过简单的数学运算得出的(10G/100M)。
这个做法的一个很明显的副作用就是,万一有一个程序正走上了一条耗尽内存的不归路怎么办。这会导致低可用内存的情况,也就是没有内存页能够再分配给进程了。你可能也碰到过这种情况,没有 root 帐户你是杀不掉这种顽固的进程的。为了解决这一情况,终结者被激活了,并找出了要终结的进程。
关于 “Out of memory killer” 参数的调整,可以参考下 这篇文章
- Capacity Tuning | Red Hat Product Documentation
https://docs.redhat.com/en/documentation/Red_Hat_Enterprise_Linux/6/html/Performance_Tuning_Guide/s-memory-captun.html
是谁触发了 OOM Killer?
虽然现在已经知道发生了什么,但还是搞不清楚到底是谁触发了这个终结者,然后在早晨 5 点钟把我吵醒。进一步的分析后找到了答案:
-
/proc/sys/vm/overcommit_memory
中的配置允许内存的超量使用 —— 该值设置为 1,这意味着每个malloc ()
请求都会成功。 -
应用程序运行在一台 EC2 m1.small 的实例上。EC2 的实例默认是禁用了交换分区的。
这两个因素正好又赶上了我们服务的突然的流量高峰,最终导致应用程序为了支持这些额外的用户而不断请求更多的内存。内存超量使用的配置允许这个贪心的进程不停地申请内存,最后会触发这个内存不足的终结者,它就是来履行它的使命的。去杀掉了我们的程序,然后在大半夜把我给叫醒。
示例
当我把这个情况描述给工程师的时候,有一位工程师觉得很有意思,因此写了个小的测试用例来重现了这个问题。你可以在 Linux 下编译并运行下面这个代码片段(我是在最新的稳定版 Ubuntu 上运行的)。
package eu.plumbr.demo;
public class OOM {
public static void main(String[] args){
java.util.List l = new java.util.ArrayList();