OutOfMemoryError系列(8): Kill process or sacrifice child

一言不合就杀进程。。。

这是本系列的第八篇文章, 相关文章列表:

为了理解这个错误,我们先回顾一下操作系统相关的基础知识。

我们知道, 操作系统(operating system)构建在进程(process)的基础上. 进程由内核作业(kernel jobs)进行调度和维护, 其中有一个内核作业称为 “Out of memory killer(OOM终结者)”, 与本节所讲的 OutOfMemoryError 有关。

Out of memory killer 在可用内存极低的情况下会杀死某些进程。只要达到触发条件就会激活, 选中某个进程并杀掉。 通常采用启发式算法, 对所有进程计算评分(heuristics scoring), 得分最低的进程将被 kill 掉。因此 Out of memory: Kill process or sacrifice child 和前面所讲的 OutOfMemoryError 都不同, 因为它既不由JVM触发,也不由JVM代理, 而是系统内核内置的一种安全保护措施。

out of memory linux kernel

如果可用内存(含swap)不足, 就有可能会影响系统稳定, 这时候 Out of memory killer 就会设法找出流氓进程并杀死他, 也就是引起 Out of memory: kill process or sacrifice child 错误。

原因分析

默认情况下, Linux kernels(内核)允许进程申请的量超过系统可用内存. 这是因为,在大多数情况下, 很多进程申请了很多内存, 但实际使用的量并没有那么多.
有个简单的类比, 宽带租赁的服务商, 可能他的总带宽只有 10Gbps, 但却卖出远远超过100份以上的 100Mbps 带宽, 原因是多数时候, 宽带用户之间是错峰的, 而且不可能每个用户都用满服务商所承诺的带宽。

这样的话,可能会有一个问题, 假若某些程序占用了大量的系统内存, 那么可用内存量就会极小, 导致没有内存页面(pages)可以分配给需要的进程。可能这时候会出现极端情况, 就是 root 用户也不能通过 kill 来杀掉流氓进程. 为了防止发生这种情况, 系统会自动激活 killer, 查找流氓进程并将其杀死。

更多关于 ”Out of memory killer“ 的性能调优细节, 请参考: RedHat 官方文档.

现在我们知道了为什么会发生这种问题, 那为什么是半夜5点钟触发 “killer” 发报警信息给你呢? 通常触发的原因在于操作系统配置. 例如, /proc/sys/vm/overcommit_memory 配置文件的值, 指定了是否允许所有的 malloc() 调用成功. 请注意, 在各操作系统中, 这个配置对应的 proc 文件路径可能不同。

过量使用(overcommitting)配置, 允许流氓进程申请越来越多的内存, 最终惹得 ”Out of memory killer“ 出来搞事情。

示例

在Linux上(如最新稳定版的Ubuntu)编译并执行以下的示例代码:

package eu.plumbr.demo;

public class OOM {

public static void main(String[] args){
  java.util.List<int[]> 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();
      }
    }
  }
}

将会在系统日志中(如 /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, file-rss:0kB

提示: 可能需要调整 swap 的大小并设置最大堆内存, 例如堆内存配置为 -Xmx2g, swap 配置如下:

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

解决方案

有多种处理办法。最简单的办法就是将系统迁移到内存更大的实例中。

另外, 还可以通过 OOM killer 调优, 或者做负载均衡(水平扩展,集群), 或者降低应用对内存的需求。

不太推荐的方案是加大交换空间/虚拟内存(swap space)。 试想一下, Java 包含了自动垃圾回收机制, 增加交换内存的代价会很高昂. 现代GC算法在处理物理内存时性能飞快, 但对交换内存来说,其效率就是硬伤了. 交换内存可能导致GC暂停的时间增长几个数量级, 因此在采用这个方案之前, 看看是否真的有这个必要。

原文链接: https://plumbr.eu/outofmemoryerror/kill-process-or-sacrifice-child

翻译日期: 2017年9月21日

翻译人员: 铁锚: http://blog.csdn.net/renfufei

阅读更多

没有更多推荐了,返回首页