Linux 的 OOM 终结者(Out Of Memory killer)

 

如今是早晨6点钟。已经醒来的我正在总结究竟是什么事情使得个人起床闹铃提早了这么多。故事刚开始的时候,手机铃声刚好中止。又困又烦躁的我看了下手机,看看是否是我本身疯了把闹钟调得这么早,竟然是早晨5点。然而不是,而是咱们的监控系统显示,Plumbr服务出故障了。html

做为这个领域的经验丰富的老鸟,我打开了咖啡机,这是正确解决问题的第一步。一杯咖啡在手以后,如今我能够开始处理故障了。首先要怀疑的是应用程序自己,由于它在崩溃以前一点异常也没有。应用程序日志中没有错误,没有警告,也没有任何可疑的信息。java

咱们部署的监控系统发现进程已经挂掉了并重启了服务。因为如今咖啡因已经流淌在个人血液中了,我开始变得信心十足。果真在30分钟后,我在/var/log/kern.log日志中发现了下面的信息:linux

  1. Jun 4 07:41:59 plumbr kernel: [70667120.897649] Out of memory: Kill process 29957 (java) score 366 or sacrifice child
  2. 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)的内建机制,在内存太低的状况下,它会杀掉你的进程。当探测到这一状况时,这个终结者会被激活,而后挑选出一个进程去终结掉。选择目标进程使用的是一套启发式算法,它会计算全部进程的分数,而后选出那个分数最低的进程。算法

理解”Out of memory killer“

默认状况下,Linux内核会容许进程请求的内存超出实际可用内存的大小。这在现实世界中是有意义的,由于大多数进程其实并不会用到全部分配给它的内存(注:同一时间内不会全用到)。和这个问题最相似的就是运营商了。他们承诺卖给用户的都是100Mb的带宽,这实际上远远超出了他们的网络容量。他们赌的就是用户实际上并不会同时用完分配给他们的下载上限。一个10Gb的链接能够很轻松地承载100个以上的用户,这里的100是经过简单的数学运算得出的(10G/100M)。网络

这个作法的一个很明显的反作用就是,万一有一个程序正走上了一条耗尽内存的不归路怎么办。这会致使低可用内存的状况,也就是没有内存页可以再分配给进程了。你可能也碰到过这种状况,没有root账户你是杀不掉这种顽固的进程的。为了解决这一状况,终结者被激活了,并找出了要终结的进程。ide

关于"Out of memory killer"参数的调整,能够参考下这篇文章测试

是谁触发了Out of memory killer?

虽然如今已经知道发生了什么,但仍是搞不清楚究竟是谁触发了这个终结者,而后在早晨5点钟把我吵醒。进一步的分析后找到了答案:ui

  • /proc/sys/vm/overcommit_memory中的配置容许内存的超量使用——该值设置为1,这意味着每一个malloc()请求都会成功。
  • 应用程序运行在一台EC2 m1.small的实例上。EC2的实例默认是禁用了交换分区的。

这两个因素正好又遇上了咱们服务的忽然的流量高峰,最终致使应用程序为了支持这些额外的用户而不断请求更多的内存。内存超量使用的配置容许这个贪心的进程不停地申请内存,最后会触发这个内存不足的终结者,它就是来履行它的使命的。去杀掉了咱们的程序,而后在大半夜把我给叫醒。spa

示例

当我把这个状况描述给工程师的时候,有一位工程师以为颇有意思,所以写了个小的测试用例来重现了这个问题。你能够在Linux下编译并运行下面这个代码片断(我是在最新的稳定版Ubuntu上运行的)。日志

package eu.plumbr.demo;

public class OOM {

        public static void main(String[] args){

                  java.util.List l = new java.util.ArrayList();

                  for (int i = 10000; i < 100000; i++) {

                          try {

                                  l.add(new int[100_000_000]);

                        } catch (Throwable t) {

                                  t.printStackTrace();

                        }

                }

        }

}

 

而后你就会发现一样的一个 Out of memory: Kill process (java) score or sacrifice child信息。

注意的是,你可能得调整下交换分区以及堆的大小,在我这个测试用例中,我经过-Xm2g设置了2G大小的堆,同时交换内存使用的是以下的配置:

swapoff -a dd if=/dev/zero of=swapfile bs=1024 count=655360 mkswap swapfile swapon swapfile

 

解决方案?

这种状况有好几种解决方案。在咱们这个例子中,咱们只是把系统迁移到了一台内存更大的机器上(裤子都脱了就让我看这个?)我也考虑过激活交换分区,不过咨询了工程师以后我想起来JVM上的GC进程在交换分区下的表现并非很理想,所以这个选项就做罢了。

还有别的一些方法好比OOM killer的调优,或者将负载水平分布到数个小的实例上,又或者减小应用程序的内存占用量。

 

参考:

Linux调优:https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/6/html/performance_tuning_guide/s-memory-captun

Linux设置swap: https://www.cnblogs.com/kerrycode/p/5246383.html

相关文章
相关标签/搜索