微信号:sagacity-mac

介绍:MacTalk 开通于2012年末,内容起于 Mac 而不止 Mac,内容覆盖了技术、创业、产品和人文思考.文风有趣,又有一点力量.相关图书《MacTalk·人生元编程》《MacTalk·跨越边界》

每个程序员都该了解一点 Linux 内存管理知识

2018-05-14 20:16 池建强

题图:by globe_people from Instagram

上周写了篇文章,介绍了一个 Java 内存溢出的案例,创下了今年阅读量最低记录(不算图片),为了挽回颜面,我决定再写一篇,还是从案例入手,介绍 Linux 内存管理机制和一个内存溢出杀手的存在感。

这是以前工作中的一个真实案例:运维人员突然发现有个在线服务失效了。一般遇到这种情况,运维和相关的研发人员都会查看在线的日志服务,必要的话还会登录到生产环境的服务器上去一看究竟。这次略有不同。

通过 ping 可以知道这台服务器还「活着」,但是无法通过 ssh 登录,网络链路确认了没有问题,在线系统日志也查不出什么端倪。排除了所有的可能性之后,我们只能认为,这台服务器不仅干掉了我们部署的服务,而且干掉了 sshd 进程。

由于这个服务没有那么敏感,所以运维人员选择了直接重启服务器(一台虚拟机),系统启动后一切都恢复了正常,杀手隐藏在服务器的 shadow 中,就像一切都没有发生过。登上服务器查看系统日志 /var/log/messages,可以看到类似这样的信息:

Out of memory: Kill process 862 (xxproid) score 11 or sacrifice child

这条消息表明,某个 pid 是 862 的进程被 OOM Killer 杀掉了,再往下找,还可以找到 sshd 的进程名。在内存极度紧张的时候, sshd 这种不怎么占内存的进程也无法幸免。覆巢之下,焉有完卵。

OOM Killer 的全称叫 Out Of Memory Killer,今天简单给大家介绍下,它是 linux 内核的内存保护机制。当应用程序大量请求内存导致内存不足的时候,通常会触发 OOM Killer,OOM Killer 会根据相对简单粗暴的算法杀掉某个进程以解燃眉之急。如果杀掉一个进程还不足以解决问题,Killer 会按照它对进程的评分,逐次干掉,分数越高,被干掉的几率越大,直到内存可以正常使用。

为什么会出现这种情况呢?这要从 Linux 的内存管理策略说起。

操作系统里内存管理的主要作用是,进程请求内存的时候为其分配可用内存,进程释放后回收内存,并监控内存的使用状况。最简单的内存管理方式是所有运行的进程对所有内存具备访问权限,这种方式下,进程必须包含对所需硬件操作的全部代码,能找到对应的内存地址,然后将自己的数据载入内存。以这种方式进行开发,内存的利用率上不去不说,每个程序员差不多都会被内存管理编程搞到吐血,所以这件事得交给操作系统来做。

现代操作系统需要程序能够共享内存,并且内存的限制对开发者透明,有些程序占用了内存空间,但不一定是一直使用的,这样可以把这部分内存数据序列化到磁盘上,需要的时候再加载到内存里,这样内存资源永远会给最需要的程序使用。有了需求,自然会有解决办法,程序员们为了拯救自己,可以做出各种匪夷所思的技术,他们发明了虚拟内存(Virtual Memory)。虚拟内存技术支持程序访问比物理内存大得多的内存空间,也使得多个程序共享内存更加高效。物理内存由 RAM 芯片提供,虚拟内存则依靠透明的使用磁盘空间,使程序运行起来好像有了更大的内存空间。虽然是个错觉,但是程序们都很开心,程序员们也很开心,但幸福的时光总是短暂的,过度分配内存(over-commit memory)的方式也会带来麻烦。

当大部分程序都处于「工作状态」的时候(例如高并发),大量的进程会向操作系统申请内存,这就导致了内存的需求加起来在某个瞬间超过了物理内存,怎么处理这种情况呢?难不倒程序员,他们又为此设计了一位杀手,起名内存溢出杀手 —— OOM Killer。当内存溢出的时候,OOM Killer 会冷静的挑一些进程出来,残忍杀害,以保证整个系统正常运行。这时候的内存管理就像银行挤兑,人们把现金存入银行,收取一定的利息,平时只有少数人去银行取现,银行会拿人们存的钱去做更有价值的投资。但是,如果大部分人都去银行取现,银行是没有那么多现金的。挤兑,往往会导致银行倒闭,毕竟银行没办法请杀手干掉那么多的挤兑群众。现在的 P2P 理财也是一个道理,投资者都去变现,无论是多么良性的资产,一样玩完。

OOM Killer 的调用链路大致是这样的,分配内存,发现内存不足,这时候系统会调用 OOM Killer 的 out_of_memory 方法,通过 select_bad_process 找到评分最高的进程,干掉。

每个进程的分数存储在 /proc/$pid/oom_score,这个评分是程序通过各种因子计算出来的,比如进程起始占用的虚拟内存,进程的子进程(如果有的话)对内存的使用情况,对 cpu 的使用时间,程序运行时间,进程间交互等。大的方向就是,你用内存越狠越频繁,评分越高,当发生内存溢出的时候越容易被杀掉。

具体的代码和算法可以参考 mm/oom_kill.c,注释写的很清楚。
https://github.com/torvalds/linux/blob/master/mm/oom_kill.c

这时候的另一个矛盾是,大部分重要的应用程序,都会频繁的大量使用内存,如何避免重要的进程被少掉呢?OOM Killer 提供了一个分数调节机制。修改 /proc/$pid/oom_score_adj 的调整分数,可以降低该进程被 kill 的几率。由于 score 的分值范围在 [-17, 15],本着分值越大约容易被杀死的原则,为一个进程设置 adj 为 -17,基本上可以保证该进程是安全的。

如果查看分数排名最高的进程呢?可以用下面这个脚本:

关于 Linux 内存管理更详细的内容,可以参考这篇文章(或者直接去读英文官方文档)
http://learning-kernel.readthedocs.io/en/latest/mem-management.html

当然了,解决内存不足问题的好办法之一是增加物理内存,内存多了,很多问题就不是问题了。随着硬件的发展,之前遇到的很多软件问题和程序员需要穷尽聪明才智解决的问题,都慢慢消失了,这也是一种生活。


最后一期互相:腾讯 2018云 + 未来峰会将于 5 月 23 号在广州召开,腾讯小马哥会出席这个会议,据说张小龙也在现场。我和鸟哥等人会去参加个开发者专场,欢迎你一同前往。

如何把握云计算红利?来听业界大咖干货(腾讯互选,点击查看详情)

 
MacTalk 更多文章 想成功?你得放弃这13件事 Mac 上最好的 Markdown 笔记软件 不要做一个 Hater 诡异的 Java OutOfMemory 网易是如何做产品的?
猜您喜欢 关于AIOps智能运维,你必须知道这些事! 一个比较折腾的应用启动失败的排查case Android 进程间通讯:深入理解 Messenger 的实现细节 「黄泽瑄的成长记」第五季来啦 刘睿民:数据库战国时代,我不跟你们玩政治!