转自 http://www.361way.com/linux-oom/6229.html
一、OOM killer算法
OOM是linux out of memory的简称,由于现网要做此类问题的优化,这里再总结下。OOM的算法策略具体见下图:
二、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杀死它的可能性降低。
- # cat /proc/12465/oom_score
- 79872
- # echo ‐5 > /proc/12465/oom_adj
- # cat /proc/12465/oom_score
- 78
还有一个特殊值-17,它会为该进程禁用oom_killer。在下面的示例中,oom_score返回值0,表示该进程不会被杀死。
- # cat /proc/12465/oom_score
- 78
- # echo ‐17 > /proc/12465/oom_adj
- # cat /proc/12465/oom_score
- 0
所以可以把重要进程的值设置为-17 。
四、swap增加的必要性
1、CommitLimit与Committed_AS
先看下下面两个值:
- # grep -i commit /proc/meminfo
- CommitLimit: 6026080 kB
- Committed_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之前:
增加swap之后:
而实际swap占用的内存只有几十M,如果担心swap的分配为影响IO情能,可以再修改vm.swappiness 参数的大小。
参考页面:
https://access.redhat.com/mt/zh-hans/solutions/20985
https://blog.csdn.net/run_for_belief/article/details/83446344