一、OOM killer算法

OOM是linux out of memory的简称,由于现网要做此类问题的优化,这里再总结下。OOM的算法策略具体见下图:

linux-oom-killer
linux-oom-killer

二、OOM机制

Linux用户内存都是读写时分配,所以系统发现需要内存基本上都是发生在handle_mm_fault()的时候(其他特殊流程类似,这里忽略),handle_mm_fault()要为缺的页分配内存,就会调alloc_pages()系列函数,从而调prepare_alloc_pages(),进而进入__alloc_pages_direct_reclaim(),这里已经把可以清到磁盘上的缓冲都清了一次了。这样之后还是分配不到内存,就只好进入OMM Killer了(pagefault_out_of_memory())。

上面这段话是从知乎上看到的别人的分析,有兴趣的可以去源码。OOM本身说白了是为保证系统自身的正常运行而作的一个优化处理手段,但他也有一些问题,下面通俗的看下:

系统中的内存只有可能被正在运行的进程和内核占据了,大家都不让,系统就只有死。内核是官家,进程是商家,官家不能杀,只好杀商家,商家杀一个也是杀,杀十个也是杀,那就杀个最胖的,少拉点仇恨。也就只能这样了,换你,你能怎么选?

这样做的缺点:一个程序的行为有可能使另一个正常程序”躺枪“(一个进程A狂分配内存,导致系统把毫无关系的进程B给kill了)。

三、OOM调优

1、能否禁用OOM机制

Red Hat Enteprise Linux 5、6和7无法完全禁用OOM-KILLER。可以通过overcommit_memory参数设置分配上限。

  • overcommit_memory=0, 表示内核将检查是否有足够的可用内存供应用进程使用;如果有足够的可用内存,内存申请允许;否则,内存申请失败,并把错误返回给应用进程。
  • overcommit_memory=1, 表示内核允许分配所有的物理内存,而不管当前的内存状态如何。
  • overcommit_memory=2, 表示内核允许分配超过所有物理内存和交换空间总和的内存

为什么会考虑调整****overcommit_memory?

一个保守的操作系统不会允许memory overcommit,有多少就分配多少,再申请就没有了,这其实有些浪费内存,因为进程实际使用到的内存往往比申请的内存要少,比如某个进程malloc()了200MB内存,但实际上只用到了100MB,按照UNIX/Linux的算法,物理内存页的分配发生在使用的瞬间,而不是在申请的瞬间,也就是说未用到的100MB内存根本就没有分配,这100MB内存就闲置了。

默认情况下系统是保守的,值为overcommit_memory=0。该项我觉得设成1是可以的,但是不建议使用参数值2,参数设置为允许所有的内存分配(含SWAP)—– 即使主机可能已经无法响应了。因为这样可能会造成机器完全无响应,官家和商家一起同归于尽。

后面会说他其他调整内存允许上限的方法,具体可以见下面部分。

2、尽少重要进程被OOM-killer的机率

为避免上面提到的A有问题把B杀了的情况,可以通过设置优先级,优先保证核以业务进程B不被杀掉。可以通过调整oom_killer得分来确定哪些进程被杀死。在/ proc / PID /中,有两个标记为oom_adj和oom_score的工具。 oom_adj的有效分数在-16到+15之间。该值用于使用一种算法来计算流程的“不良”程度,该算法还考虑了流程运行了多长时间以及其他因素。要查看当前的oom_killer得分,请查看该过程的oom_score。 oom_killer将首先杀死得分最高的进程。

示例调整PID为12465的进程的oom_score,以使oom_killer杀死它的可能性降低。

1# cat /proc/12465/oom_score
279872
3# echo ‐5 > /proc/12465/oom_adj
4# cat /proc/12465/oom_score
578

还有一个特殊值-17,它会为该进程禁用oom_killer。在下面的示例中,oom_score返回值0,表示该进程不会被杀死。

1# cat /proc/12465/oom_score
278
3# echo ‐17 > /proc/12465/oom_adj
4# cat /proc/12465/oom_score
50

所以可以把重要进程的值设置为-17 。

四、swap增加的必要性

1、CommitLimit与Committed_AS

先看下下面两个值:

1# grep -i commit /proc/meminfo
2CommitLimit:     6026080 kB
3Committed_AS:    7710484 kB

CommitLimit 就是overcommit的阈值,申请的内存总数超过CommitLimit的话就算是overcommit。

Committed_AS 表示所有进程已经申请的内存总大小,(注意是已经申请的,不是已经分配的),如果 Committed_AS 超过 CommitLimit 就表示发生了overcommit,超出越多表示 overcommit 越严重。Committed_AS 的含义换一种说法就是,如果要绝对保证不发生OOM (out of memory) 需要多少物理内存

这里有两个问题:虽然审请了,系统给不给是一会事(overcommit_memory);另外用不用得完,比如一个JAVA_OPTS中设置了Xmx为8G,并不表示最大申请这么多,java就一定实时达到了这个最大值。所以Committed_AS大于CommitLimit也不一定都是不正常,但尽量不要大于后者。

2、CommitLimit是如何算出来的?

CommitLimit 它既不是物理内存的大小,也不是free memory的大小,它是通过内核参数vm.overcommit_ratio或vm.overcommit_kbytes间接设置的,公式如下:

CommitLimit = (Physical RAM * vm.overcommit_ratio / 100) + Swap

vm.overcommit_ratio 是内核参数,缺省值是50,表示物理内存的50%。如果你不想使用比率,也可以直接指定内存的字节数大小,通过另一个内核参数 vm.overcommit_kbytes即可;如果使用了huge pages,那么需要从物理内存中减去,公式变成:

CommitLimit = ([total RAM] - [total huge TLB RAM]) * vm.overcommit_ratio / 100 + swap

所以由上面的公式可以推出,在没有swap的情况下,进程耗用的内存又比较多的时候,很容易造成Committed_AS达到CommitLimit的上限,甚至超过这个上限。那如减小系统的危机感呢?方法如下:

  • 增大overcommit_ratio值,由默认50,可以提高为100(或者修改vm.overcommit_kbytes值);
  • 增加swap(在没有swap的主机上)
  • 修改vm.overcommit_memory 的值为2

显然在没有swap的主机,增加swap的方案相对最优。增加后,可以提高CommitLimit,同时又可以在主机确实内存不足时,临时有磁盘IO充当内存分配给进程临时用。

五、增加swap前后%commit的变化

%commit = Committed_AS/(MemTotal+SwapTotal)

意思是内存申请物理内存与交换区之和的百分比。以下是在增加swap前后,主机上该值的变化(该值在一定程序上反映了主机的危机感强弱)

未增加swap之前:

commit-noswap
commit-noswap

增加swap之后:

commit-haveswap
commit-haveswap

而实际swap占用的内存只有几十M,如果担心swap的分配为影响IO情能,可以再修改vm.swappiness 参数的大小。

参考页面:

https://access.redhat.com/mt/zh-hans/solutions/20985

http://linuxperf.com/?p=102

https://blog.csdn.net/run_for_belief/article/details/83446344