OOM机制
OOM指当程序占用内存超过系统内存或系统可调用的内存时,触发LINUX 内核机制(OOM killer),导致某些进程被kill掉;举个栗子,公司里同时有小明与小斌等若干员工,其中公司内部有一个打分机制,就是员工的干饭量(内存),本身公司是能负担所有员工,但这时小明与小斌尤为突出,突然之间干饭量孟增,为了保证公司不倒闭以及维持其他人能继续干饭,公司就通过这个干饭量进行打分,谁的分高就开除谁,然后发现,小明最能干饭,那么公司就把小明开了
OOM打分
Linux内核有个机制叫OOM killer(Out-Of-Memory killer),该机制会监控那些占用内存过大,尤其是瞬间很快消耗大量内存的进程,为了防止内存耗尽内核会把该进程杀掉。
内核检测到系统内存不足、挑选并杀掉某个进程的过程可以参考内核源代码 linux/mm/oom_kill.c,当系统内存不足的时候,out_of_memory()被触发,然后调用 select_bad_process() 选择一个“bad”进程杀掉,判断和选择一个“bad”进程的过程由 oom_badness()决定,最 bad 的那个进程就是那个最占用内存的进程。
这里附上一段源代码
/**
* oom_badness -heuristic function to determine which candidate task to kill
* @p: taskstruct of which task we should calculate
* @totalpages:total present RAM allowed for page allocation
*
* The heuristicfor determining which task to kill is made to be as simple and
* predictableas possible. The goal is to return thehighest value for the
* task consumingthe most memory to avoid subsequent oom failures.
*/
unsigned long oom_badness(struct task_struct *p,struct mem_cgroup *memcg,
const nodemask_t *nodemask, unsigned longtotalpages)
{
long points;
long adj;
if (oom_unkillable_task(p, memcg, nodemask))
return 0;
p = find_lock_task_mm(p);
if (!p)
return 0;
adj = (long)p->signal->oom_score_adj;
if (adj == OOM_SCORE_ADJ_MIN) {
task_unlock(p);
return 0;
}
/*
* The baseline for thebadness score is the proportion of RAM that each
* task's rss, pagetable and swap space use.
*/
points = get_mm_rss(p->mm) + atomic_long_read(&p->mm->nr_ptes)+
get_mm_counter(p->mm, MM_SWAPENTS);
task_unlock(p);
/*
* Root processes get 3% bonus, just like the__vm_enough_memory()
* implementation used by LSMs.
*/
if (has_capability_noaudit(p,CAP_SYS_ADMIN))
adj -= (points * 3) / 100;
/*Normalize to oom_score_adj units */
adj *= totalpages / 1000;
points += adj;
/*
* Never return 0 for an eligible taskregardless of the root bonus and
* oom_score_adj (oom_score_adj can't beOOM_SCORE_ADJ_MIN here).
*/
returnpoints > 0 ? points : 1;
}从上面的 oom_kill.c 代码里可以看到 oom_badness() 给每个进程打分,根据 points 的高低来决定杀哪个进程,这个 points 可以根据 adj 调节,root 权限的进程通常被认为很重要,不应该被轻易杀掉,所以打分的时候可以得到 3% 的优惠(分数越低越不容易被杀掉)。我们可以在用户空间通过操作每个进程的 oom_adj 内核参数来决定哪些进程不这么容易被 OOM killer 选中杀掉。
OOM相关参数
1、/proc/sys/vm/panic_on_oom(默认值为0)
# sysctl -w vm.panic_on_oom=1 vm.panic_on_oom = 1 //1表示关闭,默认为0表示开启OOM killer
# sysctl –p
2、/proc/sys/vm/oom_kill_allocating_task(默认值为0)
值为0:会根据OOM的打分机制来kill最占内存的进程
值为非0:会直接kill掉由于申请内存而触发OOM的进程,不通过打分机制

3、oom_adj、oom_score_adj 和 oom_score
三个参数位置默认都是在 /proc/进程PID/ 目录下

OOM会根据进程里面的这三个参数进行打分,综合所有进程得到最高分的进程将会触发OOM 被kill掉,
进程的打分机制主要包括两部分: 系统打分和用户打分
系统打分就是根据进程的物理内存消耗量,然后内核会给出分数,放在oom_score里面(根据进程的内存使用情况值会跟着变)

用户打分就是修改oom_score_adj或者oom_adj的值,然后会跟oom__score进行加法运算得出最后的分数,
(oom_score_adj可选值范围为 -1000 - 1000,)(oom_adj可选值范围为 -17 - 15,)
oom_adj是旧版本供给用户修改的值,为了兼容,目前仍然保留这个参数,修改这个值,实际上是会换算成oom_score_adj的值。
最后OOM会对比所有进程的oom_score参数来决定该kill哪个进程。
4、/proc/sys/vm/oom_dump_tasks(默认值为1)
如果将参数置为0,不会打印系统内进程的信息在/var/log/messages里面。
值为1时:当有进程被OOM的时候,会输出进程的一些信息在日志里面(/var/log/messages),这些信息可以帮助找出为什么OOM,找到导致OOM的进程,以及可以看到为什么进程会被选中,只要发生OOM,都会打印进程的信息

OOM排查
一般来说,我们会直接查看messages日志,是否存在类似out of memory字样,
#cat /var/log/messages | grep -i "out of memory"
还包括其他方法
通过dmsge查看
# dmesg | grep oom
通过journalctl查看,响应信息会标红且会包含cgroup字样
# sudo journalctl
OOM解决办法
最好的办法就是扩充资源,其次就是程序使用root执行(不推荐)
然后就是调整程序oom_adj参数为-17 (参见前面内容)
如果需要的话可以完全不允许过度分配内存,此时也就不会出现OOM的问题(不过不推荐这样做):
# sysctl -w vm.overcommit_memory=2
补充:
1.子进程会继承父进程的oom_adj。
2.OOM不适合于解决内存泄漏(Memory leak)的问题。
3.有时free查看还有充足的内存,但还是会触发OOM,是因为该进程可能占用了特殊的内存地址空间。(例如/run 下存在大文件)