简单来说,linux的内存分为虚拟内存和物理内存,进程在申请内存时,首先申请的是虚拟内存,等到真正需要的时候才去申请物理内存。内核通过这种机制避免内存的浪费,实现了按需分配物理内存。

所以虚拟内存可以大于实际的物理内存,超过这部分就是memory overcommit,而一旦进程需要申请的物理内存超过实际内存和交换空间的总和,内核就会用OOM Killer,选择杀掉一个或部分进程,保障系统和其它进程有可用的内存。

1. overcommit 设置

在 Linux 中,可以通过内核参数vm.overcommit_memory去控制是否允许 overcommit:

  • 默认值是 0,在这种情况下,只允许轻微的 overcommit,但一次申请大量内存会被禁止,root能使用的overcommit也比普通用户多
  • 设置为 1,表示总是允许 overcommit
  • 设置为 2,则表示总是禁止 overcommit,系统分配的内存不能超过系统内存+交换内存

2. OOM Killer 设置

linux内核会为每个进程算一个分数,发生OOM时分数最高的进程会被kill掉,主要看以下几个参数:

  • /proc/PID/oom_score ,OOM 最终得分,值越大越有可能被杀掉
  • /proc/PID/oom_score_adj ,取值范围为-1000到1000,计算oom_score时会加上该参数
  • /proc/PID/oom_adj ,取值是-17到+15,该参数主要是为兼容旧版内核

只要允许overcommit,就会有oom killer发生,所以关闭overcommit就可以关闭OOM Killer:

sysctl -w vm.overcommit_memory=2
echo "vm.overcommit_memory=2" >> /etc/sysctl.conf

如果不想关闭OOM,也可以直接调整相应进程的oom_score_adj和oom_adj值,来按需进行设置OOM的优先级:

echo -1000 > /proc/PID/oom_score_adj 

或者:

echo -17 > /proc/PID/oom_adj 

3. 找出最可能产生OOM的进程

通过脚本计算一下所有进程的oom_score和oom_score_adj,就可以找出最可能产生OOM的进程,系统进程的OOM score是0,需要排除,脚本是网上找的,内容如下:

#!/bin/bash

printf 'PID\tOOM_Score\tOOM_Adj\tCommand\n'

while read -r pid comm; 
do
     [ -f /proc/$pid/oom_score ] && [ $(cat /proc/$pid/oom_score) != 0 ] && printf '%d\t%d\t\t%d\t%s\n' "$pid" "$(cat /proc/$pid/oom_score)" "$(cat /proc/$pid/oom_score_adj)" "$comm"; 
done < <(ps -e -o pid= -o comm=) | sort -k 2nr

4. 手动触发OOM killer

学习的时候需要测试OOM,可以用以下方法手动触发OOM

  1. 前台执行一个循环,不停的为变量赋值,最终就会耗光内存
    for b in {0..99999999}; do a=$b$a; done
  1. 多次循环触发OOM-killer
    for x in {1..10}; do echo "Start trigger OOM-killer: $x"; bash -c "for b in {0..99999999}; do a=$b$a; done"; done
  1. 基本上面的方式,边触发OOM-killer,边监控OOM score
    #!/bin/bash

    oom(){
        echo "Start trigger OOM-killer" && bash -c "for b in {0..99999999}; do a=$b$a; done"
    }

    oom &

    ppid=`ps -ef |grep -v grep |grep  99999999 |awk '{print $3}'`
    pid=`pgrep -P $ppid`

    echo ppid: $ppid
    echo fork pid: $pid

    while [ -d "/proc/$pid" ];
    do
        echo $pid :  oom_score: `cat /proc/$pid/oom_score`
    done

5. OOM监控

OOM发生时,会在/var/log/message中出现如下日志:

Nov 26 10:51:25 nixops.me kernel: Out of memory: Kill process 18903 (bash) score 645 or sacrifice child
Nov 26 10:51:25 nixops.me kernel: Killed process 18903, UID 0, (bash) total-vm:3640712kB, anon-rss:3441992kB, file-rss:4kB

zabbix之类的监控软件可以通过关键字过滤,来进行监控。如果有ELK这类的日志系统,可以收集messages日志,用logstash插件进行告警。

参考文章:
https://askubuntu.com/questions/1188024/how-to-test-oom-killer-from-command-line/1188169#1188169
https://learning-kernel.readthedocs.io/en/latest/mem-management.html