Linux 上的 OOM 杀手时不时地会对各种应用程序造成严重破坏,而且内核开发方面似乎并没有做太多工作来改善这种情况。在设置新服务器,以反转内存过量使用的默认设置,即关闭它(vm.overcommit_memory=2
),除非您知道自己需要将其用于特定用途?您知道需要过量使用的用例是什么?
作为奖励,由于在vm.overcommit_memory=2
取决于vm.overcommit_ratio
交换空间的情况下的行为,那么确定后两者的大小的良好经验法则是什么,以便整个设置保持合理的运行?
答案1
一个有趣的类比(来自http://lwn.net/Articles/104179/):
一家航空公司发现,如果飞机上载的燃油较少,飞行成本会更低。这样飞机重量会更轻,耗油量也会更少,从而节省资金。然而,在极少数情况下,燃油不足,飞机就会坠毁。该公司的工程师通过开发一种特殊的 OOF(燃油耗尽)机制解决了这个问题。在紧急情况下,会选择一名乘客并将其抛出飞机。(必要时,会重复该程序。)人们开发了大量理论,并发表了许多出版物,探讨如何正确选择要被抛出的受害者。应该随机选择受害者吗?还是应该选择最重的人?还是最老的人?乘客是否应该付费以免被抛出,这样受害者就会是机上最穷的人?例如,如果选择了最重的人,是否应该有一个特殊例外,以防飞行员?头等舱乘客是否应该免于被抛出?既然 OOF 机制已经存在,它会不时启动,即使没有燃油短缺,也会将乘客抛出。工程师们仍在研究此次故障的具体原因。
答案2
OOM 杀手只会在系统超载时造成严重破坏。给它足够的交换空间,不要运行突然决定消耗大量 RAM 的应用程序,这样就不会有问题了。
具体回答您的问题:
- 我认为在一般情况下关闭过度使用不是一个好主意;很少有应用程序能够正确处理
brk
(2)(以及使用它的包装器,例如malloc
(3))返回错误的情况。当我在以前的工作中尝试这样做时,人们认为让所有东西都能够处理内存不足错误比处理 OOM 的后果更麻烦(在我们的例子中,如果发生 OOM,这比不得不重新启动偶尔的服务要糟糕得多——我们必须重新启动整个集群,因为 GFS 是一堆冒着热气的粪便)。 - 您希望对任何过度使用内存的进程启用过度使用。这里最常见的两个罪魁祸首是 Apache 和 JVM,但很多应用程序或多或少都会这样做。它们思考他们可能在将来的某个时间点需要大量内存,因此它们会立即占用一大块内存。在启用过量使用的系统上,内核会说“嗯,随便吧,当你真正想要写入这些页面时来打扰我”,不会发生任何不好的事情。在关闭过量使用的系统上,内核会说“不,你不能有那么多内存,如果你碰巧在将来的某个时间点写入所有内存,我就没戏了,所以没有内存给你!”,分配失败。由于没有什么有人说“哦,好的,我可以拥有这个较小的进程数据段吗?”,然后进程要么 (a) 因内存不足错误而退出,要么 (b) 不检查 malloc 的返回代码,认为可以继续,并写入无效的内存位置,从而导致段错误。幸运的是,JVM 在启动时会完成所有预分配(因此您的 JVM 要么立即启动,要么立即死亡,您通常会注意到这一点),但 Apache 会对每个新子进程执行一些奇怪的操作,这可能会在生产中产生令人兴奋的效果(不可重现的“不处理连接”类型的兴奋)。
- 我不想将 overcommit_ratio 设置为高于默认值 50%。同样,根据我的测试,虽然将其设置为 80 或 90 左右可能会声音就像一个很酷的想法,内核在不方便的时候需要大量内存,而具有高过量使用率的满载系统很可能在内核需要时没有足够的备用内存(导致恐惧、瘟疫和错误)。因此,使用过量使用引入了一种新的、甚至更有趣的故障模式——当您的内存耗尽时,不仅仅是重新启动任何被 OOM 的进程,现在您的机器崩溃了,导致机器上的所有东西都停止运行。太棒了!
- 无过量使用系统中的交换空间取决于您的应用程序需要多少已请求但未使用的内存,以及健康的安全裕度。计算出特定情况下需要多少内存留给读者练习。
基本上,我的经验是,关闭过度使用是一个很好的实验,但在实践中很少像理论上那样有效。这与我对内核中其他可调参数的经验非常吻合——Linux 内核开发人员几乎总是比你聪明,而默认设置对大多数人来说效果最好,广阔的大多数情况下,不要管它们,而是去查找哪个进程有泄漏并修复它。
答案3
嗯,我不太认同支持过度使用和 OOM killer 的论点...当 Womble 写道:
“OOM 杀手只会在系统超载时造成严重破坏。给它足够的交换空间,不要运行突然决定消耗大量 RAM 的应用程序,这样你就不会遇到问题。”
他描述的是一个环境场景,其中过量使用和 OOM 杀手未强制执行,或者“实际上”不执行(如果所有应用程序都根据需要分配内存,并且有足够的虚拟内存可供分配,则内存写入将紧跟内存分配而不会出现错误,因此即使启用了过量使用策略,我们也无法真正谈论过量使用的系统)。这隐含地承认,当不需要干预时,过量使用和 OOM 杀手效果最佳,据我所知(我承认我说不出太多……),大多数支持该策略的人都认同这一点。此外,提到预分配内存时具有特定行为的应用程序,让我想到可以在分布级别调整特定处理,而不是采用基于启发式方法的默认系统方法(我个人认为启发式方法对于内核来说不是一种很好的方法)
对于 JVM 来说,它是一个虚拟机,在某种程度上,它需要分配它的所有资源需求在启动时,它可以为其应用程序创建“虚假”环境,并尽可能将其可用资源与主机环境分开。因此,最好让它在启动时失败,而不是在一段时间后由于“外部”OOM 条件(由过度使用/OOM 杀手/其他原因引起)而失败,或者无论如何因为这种条件干扰其自己的内部 OOM 处理策略而受到影响(一般来说,虚拟机应该从一开始就获得任何所需的资源,主机系统应该“忽略”它们直到最后,就像与显卡共享的任何物理内存都不会 - 也不能 - 被操作系统触及一样)。
关于 Apache,我怀疑偶尔关闭并重新启动整个服务器是否比让单个子服务器以及单个连接从一开始就失败(= 子服务器/连接)更好(就好像它是另一个实例运行一段时间后创建的全新 JVM 实例)。我猜最好的“解决方案”可能取决于特定的环境。例如,考虑电子商务服务,有时让几个与购物图表的连接随机失败可能比丢失整个服务更可取,因为整个服务存在风险,例如,中断正在进行的订单完成,或者(可能更糟)中断支付流程,并产生所有后果(可能无害,但可能有害 - 并且可以肯定的是,当问题出现时,这些问题比调试时无法重现的错误情况更糟糕)。
同样,在工作站上,消耗最多资源的进程(因此成为 OOM 终止程序的首选)可能是内存密集型应用程序,例如视频转码器或渲染软件,这可能是用户唯一希望不受影响的应用程序。这些考虑让我想到 OOM 终止程序的默认策略过于激进。它使用“最差匹配”方法,这在某种程度上类似于某些文件系统的方法(OOMK 会尝试释放尽可能多的内存,同时减少终止的子进程数量,以防止在短时间内进行任何进一步的干预,并且 fs 可以为某个文件分配比实际需要更多的磁盘空间,以防止在文件增大时进行任何进一步的分配,从而在一定程度上防止碎片化)。
但是,我认为相反的策略(例如“最佳适应”方法)可能更可取,这样就可以释放某个点所需的确切内存,而不必担心“大”进程,因为“大”进程可能会浪费内存,但也可能不会,而且内核无法知道这一点(嗯,我可以想象,跟踪页面访问次数和时间可以提示进程是否正在分配不再需要的内存,因此可以猜测进程是在浪费内存还是只是使用了大量内存,但访问延迟应该在 CPU 周期上加权,以区分内存浪费和内存和CPU 密集型应用程序,但是,虽然可能不准确,但这种启发式方法可能会产生过多的开销)。
此外,终止较少的可能进程可能并不总是一个好选择。例如,在桌面环境中(让我们想象一下资源有限的上网本或上网本),用户可能正在运行带有多个选项卡的浏览器(因此,内存消耗 - 让我们假设这是 OOMK 的首选),加上一些其他应用程序(带有未保存数据的文字处理器、邮件客户端、pdf 阅读器、媒体播放器等),加上一些(系统)守护进程,加上一些文件管理器实例。现在,发生了 OOM 错误,并且当用户正在网络上执行被认为“重要”的事情时,OOMK 选择终止浏览器……用户会感到失望。另一方面,关闭处于空闲状态的少数文件管理器实例可以释放所需的确切内存量,同时保持系统不仅正常工作,而且以更可靠的方式工作。
无论如何,我认为应该让用户自己决定要做什么。在桌面(=交互式)系统中,这应该相对容易做到,只要保留足够的资源来要求用户关闭任何应用程序(但即使关闭几个选项卡也足够了)并处理他的选择(如果有足够的空间,一个选项可以包括创建一个额外的交换文件)。对于服务(以及一般情况),我还会考虑另外两种可能的增强功能:一种是记录 OOM 杀手干预,以及进程启动/分叉失败,以便可以轻松调试故障(例如,API 可以通知发出新进程创建或分叉的进程 - 因此,像 Apache 这样的服务器,带有适当的补丁,可以为某些错误提供更好的日志记录);这可以独立于正在进行的过度提交/OOMK 来完成;其次,但并不重要,可以建立一种机制来微调 OOMK 算法 - 我知道在某种程度上可以基于每个进程定义特定的策略,但我的目标是一个“集中式”配置机制,基于一个或多个应用程序名称(或 ID)列表来识别相关进程并赋予它们一定的重要性(根据列出的属性);这种机制应该(或至少可以)分层,以便可以有一个顶级用户定义列表、一个系统(分发)定义列表和(底层)应用程序定义的条目(因此,例如,DE 文件管理器可以指示 OOMK 安全地终止任何实例,因为用户可以安全地重新打开它以访问丢失的文件视图 - 而任何重要的操作,例如移动/复制/创建数据都可以委托给更“特权”的进程)。
此外,还可以提供一个 API,以允许应用程序在运行时提高或降低其“重要性”级别(考虑到内存管理目的,而不考虑执行优先级),例如,文字处理器可以从较低的“重要性”开始,但在刷新到文件之前保存某些数据,或者执行写入操作时提高其重要性,一旦此类操作结束,重要性再次降低(类似地,文件管理器可以在从仅仅浏览文件转变为处理数据和反之亦然时改变级别,而不是使用单独的进程,Apache 可以为不同的子进程赋予不同级别的重要性,或者根据系统管理员决定并通过 Apache 或任何其他类型的服务器的设置公开的某些策略更改子进程的状态)。当然,这样的 API 可能会被滥用/误用,但我认为这与内核任意终止进程以释放内存而没有任何有关系统正在发生的事情的相关信息(并且内存消耗/创建时间或类似信息对我来说不够相关或“验证”)相比只是一个小问题——只有用户、管理员和程序编写者才能真正确定某个进程是否由于某种原因“仍然需要”,原因是什么,以及/或应用程序是否处于导致数据丢失或其他损坏/故障的状态;但是,仍然可以做出一些假设,例如,查找某个进程获取的某种资源(文件描述符、网络套接字等)并执行待处理操作,可以判断某个进程是否应该处于比一组更高的“状态”,或者其“自我建立”的状态是否高于需要,可以降低(积极的方法,除非被用户的选择所取代,例如强制某种状态,或者通过我上面提到的列表要求尊重应用程序的选择)。
或者,只是避免过度承诺,让内核做内核必须做的事情,分配资源(但不会像 OOM 杀手那样任意拯救它们)、调度进程、防止饥饿和死锁(或从中拯救)、确保完全抢占和内存空间分离等等……
我还会花更多篇幅来谈谈过度使用方法。从其他讨论中,我得出了这样的想法:过度使用的主要问题之一(既是需要它的原因,也是可能出现问题的根源)在于分叉处理:老实说,我不知道写时复制策略究竟是如何实现的,但我认为任何激进(或乐观)的策略都可以通过类似交换的局部性策略来缓解。也就是说,除了克隆(和调整)分叉进程的代码页和调度结构之外,还可以在实际写入之前复制一些其他数据页,从父进程访问过的页面中选择更频繁地写入(即使用计数器进行写入操作)。
当然,一切,恕我直言。
答案4
如果内存被进程耗尽,以至于可能威胁到系统稳定性,那么 OOM Killer 就会出现。OOM Killer 的任务是终止进程,直到释放足够的内存,使其余进程顺利运行。OOM Killer 必须选择“最佳”进程来终止。这里的“最佳”是指终止后将释放最大内存且对系统最不重要的进程。主要目标是终止最少数量的进程,以最大限度地减少造成的损害,同时最大限度地释放内存。为了实现这一点,内核为每个进程维护 oom_score。您可以在 /proc 文件系统的 pid 目录下查看每个进程的 oom_score
# cat /proc/10292/oom_score
任何进程的 oom_score 值越高,在内存不足的情况下被 OOM Killer 杀死的可能性就越大。