xiaobaoqiu Blog

Think More, Code Less

Oomkiller

最近dev环境的Tomcat服务,还有dev环境的crate服务老是出现问题,现象就是Tomcat应用自己停了.导致测试中老是感觉莫名其妙.跟了一下原因,发现背后就是著名的oom-killer.

其实出现这个问题早就预料到了,8G内存的虚拟机器上面跑了接近20个Tomcat应用,每个Tomcat至少要求了0.5G的内存,很多要求的是默认的2G的内存.

这里主要简单介绍一下oomkiller是什么东西,原理是什么,以及对我们的影响.

1.oomkiller简介

简单的说,Linux 内核有个机制叫OOM killer(Out-Of-Memory killer),该机制会监控那些占用内存过大,尤其是瞬间很快消耗大量内存的进程(为什么后面会讲到),为了防止内存耗尽而内核会把该进程杀掉,从而腾出内存留给系统用。

现象就是相关的日志文件(/var/log/messages)里面会看到下面类似的 Out of memory: Kill process 信息(日志里面把机器host去掉了),还包括一些如pid,process name,cpu mask,trace等信息:

1
2
3
4
5
6
Aug  4 22:03:18  kernel: Out of memory: Kill process 21447 (java) score 81 or sacrifice child
Aug  4 22:03:18  kernel: Killed process 21447, UID 40001, (java) total-vm:4242176kB, anon-rss:711240kB, file-rss:1832kB
Aug  5 01:17:21  ntpd[1195]: synchronized to Xx.xx.xxx.xx, stratum 3
Aug  5 01:35:52  kernel: java invoked oom-killer: gfp_mask=0xd0, order=0, oom_adj=0, oom_score_adj=0
Aug  5 01:35:52  kernel: java cpuset=/ mems_allowed=0
Aug  5 01:35:52  kernel: Pid: 17391, comm: java Not tainted 2.6.32-358.23.2.el6.x86_64 #1

2.oomkiller原理

Linux 内核根据应用程序的要求分配内存,通常来说应用程序分配了内存但是并没有实际全部使用,为了提高性能,这部分没用的内存可以留作它用,这部分内存是属于每个进程的,内核直接回收利用的话比较麻烦,所以内核采用一种过度分配内存(over-commit memory)的办法来间接利用这部分 “空闲” 的内存,提高整体内存的使用效率。

所谓的过度分配内存,可以理解为"打白条",进程A告诉系统我需要2G内存,系统只会答应不会拒绝,因为系统认为任何一个进程不可能每时每刻都全部占用其申请的内存.所以实际上系统并没有给进程A完整的2G内存,而是需要使用的时候才临时分配.这就好像银行现金肯定远远小于所以储户的总值,因为银行认为不可能所以储户同一时间把所以的钱都取出来.

一般来说这样做没有问题,但当大多数应用程序都消耗完自己的内存的时候麻烦就来了,因为这些应用程序的内存需求加起来超出了物理内存(包括 swap)的容量,内核(OOM killer)必须杀掉一些进程才能腾出空间保障系统正常运行。

用银行的例子来讲可能更容易懂一些,部分储户取钱的时候银行不怕,银行有足够的存款应付,当全国所以的储户(或者绝大多数储户)都取钱而且每个人都想把自己钱取完的时候银行的麻烦就来了,银行实际上是没有这么多钱给大家取的。

2.1 根据什么kill

内核检测到系统内存不足,然后挑选并杀掉某个进程的过程可以参考内核源代码 linux/mm/oom_kill.c,当系统内存不足的时候,oom_kill被触发,然后选择一个进程杀掉,如何判断和选择一个进程呢,总不能随机选吧?系统会根据进程使用内存的情况给进程打分,选择进程kill的时候主要根据这个分数,打分的机制很朴素:最占用内存的进程。

每个进程的分数都由进程的oom_score记录:

1
2
3
4
[baoqiu.xiao@Xxx ~]$cat /proc/26893/oom_score
76
[baoqiu.xiao@Xxx ~]$ cat /proc/14969/oom_score
82

另外一个重要的参数就是进程的oom_adj,表示该pid进程被oom killer杀掉的权重,介于 [-17,15]之间,越高的权重,意味着更可能被oom killer选中,-17表示禁止被kill掉。默认为0.

一些其他的细节:

1.子进程会继承父进程的oom_adj;
2.OOM不适合于解决内存泄漏(Memory leak)的问题;
3.有时free查看还有充足的内存,但还是会触发OOM,是因为该进程可能占用了特殊的内存地址空间;

2.2 如何关闭oomkiller

有几种方法关闭oomkiller机制:

  • 1.调整oom_adj 将进程的oom_adj设置为17即禁止了该进程被kill.

比如系统的ssh一般是禁止oom的:

1
2
3
4
[baoqiu.xiao@ ~]$ ps aux | grep sshd
root      1208  0.0  0.0  64116   296 ?        Ss   May28   1:47 /usr/sbin/sshd
[baoqiu.xiao@ ~]$ cat /proc/1208/oom_adj 
-17

其他需要禁用,可以如下:

1
pgrep -f "sshd" | while read PID; do echo -17 > /proc/$PID/oom_adj;done

其实在oom-killer的日志中我们是可以看到未-17的一些应用:

1
2
3
4
Aug  5 01:35:52 l-crm4.des.dev.cn0 kernel: [ pid ]   uid  tgid total_vm      rss cpu oom_adj oom_score_adj name
...
Aug  5 01:35:52 l-crm4.des.dev.cn0 kernel: [ 1187]     0  1187    16029       73   2     -17         -1000 sshd
...
  • 2.修改内核参数 sysctl 下有2个可配置选项:

    vm.panic_on_oom = 0 #内存不够时内核是否直接panic,0表示开启 vm.oom_kill_allocating_task = 1 #oom-killer是否选择当前正在申请内存的进程进行kill

只要设置/etc/sysctl.conf文件中vm.panic_on_oom=1就表示关闭oom-killer(线上环境一般不推荐):

1
2
3
# sysctl -w vm.panic_on_oom=1
vm.panic_on_oom = 1   //1表示关闭,默认为0表示开启OOM
# sysctl -p

参考: http://lwn.net/Articles/317814/