Linux内核根据应用程序的要求分配内存,一般来讲应用程序分配了内存可是并无实际所有使用,为了提升性能,这部分没用的内存能够留做它用,这部份内存是属于每一个进程的,内核直接回收利用的话比较麻烦,因此内核采用一种过分分配内存(over-commit memory)的办法来间接利用这部分“空闲”的内存,提升总体内存的使用效率。通常来讲这样作没有问题,但当大多数应用程序都消耗完本身的内存的时候麻烦就来了,由于这些应用程序的内存需求加起来超出了物理内存(包括swap)的容量,内核(OOM killer)必须杀掉一些进程才能腾出空间保障系统正常运行。用银行的例子来说可能更容易懂一些,部分人取钱的时候银行不怕,银行有足够的存款应付,当全国人民(或者绝大多数)都取钱并且每一个人都想把本身钱取完的时候银行的麻烦就来了,银行其实是没有这么多钱给你们取的。html
好比某天一台机器忽然ssh远程登陆不了,但能ping通,说明不是网络的故障,缘由是sshd进程被OOM killer杀掉了。重启机器后查看系统日志/var/log/messages会发现Out of Memory:Killprocess 1865(sshd)相似的错误信息。又好比有时VPS的MySQL老是平白无故挂掉,或者VPS 常常死机,登录到终端发现都是常见的 Out of memory 问题。这一般是由于某时刻应用程序大量请求内存致使系统内存不足形成的,这时会触发 Linux 内核里的 Out of Memory (OOM) killer,OOM killer 会杀掉某个进程以腾出内存留给系统用,不致于让系统马上崩溃。若是检查相关的日志文件(/var/log/messages)就会看到下面相似的Out of memory:Kill process 信息:java
...node
Out of memory: Kill process 9682(mysqld) score 9 or sacrifice childmysql
Killed process 9682, UID 27,(mysqld) total-vm:47388kB, anon-rss:3744kB, file-rss:80kBlinux
httpd invoked oom-killer:gfp_mask=0x201da, order=0, oom_adj=0, oom_score_adj=0redis
httpd cpuset=/ mems_allowed=0算法
Pid: 8911, comm: httpd Not tainted2.6.32-279.1.1.el6.i686 #1sql
...api
21556 total pagecache pagesbash
21049 pages in swap cache
Swap cache stats: add 12819103,delete 12798054, find 3188096/4634617
Free swap = 0kB
Total swap = 524280kB
131071 pages RAM
0 pages HighMem
3673 pages reserved
67960 pages shared
124940 pages non-shared
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 选中杀掉。好比,若是不想 MySQL 进程被轻易杀掉的话能够找到 MySQL 运行的进程号后,调整 /proc/PID/oom_score_adj 为 -15(注意 points越小越不容易被杀)防止重要的系统进程触发(OOM)机制而被杀死,内核会经过特定的算法给每一个进程计算一个分数来决定杀哪一个进程,每一个进程的oom分数能够在/proc/PID/oom_score中找到。每一个进程都有一个oom_score的属性,oom killer会杀死oom_score较大的进程,当oom_score为0时禁止内核杀死该进程。设置/proc/PID/oom_adj能够改变oom_score,oom_adj的范围为【-17,15】,其中15最大-16最小,-17为禁止使用OOM,至于为何用-17而不用其余数值(默认值为0),这个是由linux内核定义的,查看内核源码可知:路径为linux-xxxxx/include /uapi/linux/oom.h。