阿里云-云小站(无限量代金券发放中)
【腾讯云】云服务器、云数据库、COS、CDN、短信等热卖云产品特惠抢购

Linux 的 OOM 终结者

115次阅读
没有评论

共计 2304 个字符,预计需要花费 6 分钟才能阅读完成。

现在是早晨 6 点钟。已经醒来的我正在总结到底是什么事情使得我的起床闹铃提前了这么多。故事刚开始的时候,手机铃声恰好停止。又困又烦躁的我看了下手机,看看是不是我自己疯了把闹钟调得这么早,居然是早晨 5 点。然而不是,而是我们的监控系统显示,Plumbr 服务出故障了。

Linux 的 OOM 终结者

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

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

  1. Jun407:41:59 plumbr kernel:[70667120.897649]Out of memory:Kill process 29957(java) score 366or sacrifice child
  2. Jun407: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 帐户你是杀不掉这种顽固的进程的。为了解决这一情况,终结者被激活了,并找出了要终结的进程。

关于 ”Out of memory killer” 参数的调整,可以参考下这篇文章。

 

是谁触发了 Out of memory killer?

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

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

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

 

示例

当我把这个情况描述给工程师的时候,有一位工程师觉得很有意思,因此写了个小的测试用例来重现了这个问题。你可以在 Linux 下编译并运行下面这个代码片段(我是在最新的稳定版 Ubuntu 上运行的)。

  1. package eu.plumbr.demo;
  2. publicclass OOM {
  3. publicstaticvoid main(String[] args){
  4. java.util.List l =new java.util.ArrayList();
  5. for(int i =10000; i <100000; i++){
  6. try{
  7. l.add(newint[100_000_000]);
  8. }catch(Throwable t){
  9. t.printStackTrace();
  10. }
  11. }
  12. }
  13. }

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

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

  1. swapoff -a
  2. dd if=/dev/zero of=swapfile bs=1024 count=655360
  3. mkswap swapfile
  4. swapon swapfile

 

解决方案?

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

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

本文永久更新链接地址:http://www.linuxidc.com/Linux/2015-07/120228.htm

正文完
星哥说事-微信公众号
post-qrcode
 0
星锅
版权声明:本站原创文章,由 星锅 于2022-01-20发表,共计2304字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
【腾讯云】推广者专属福利,新客户无门槛领取总价值高达2860元代金券,每种代金券限量500张,先到先得。
阿里云-最新活动爆款每日限量供应
评论(没有评论)
验证码
【腾讯云】云服务器、云数据库、COS、CDN、短信等云产品特惠热卖中