如果某个进程需要大量内存,系统会将所有其他进程移至交换文件。其中似乎包括 X11 服务器或终端等必要进程。
因此,如果某个进程不断分配内存且没有限制,那么一切都会变得无响应,直到该进程被 OOM-killer 杀死。我的笔记本电脑似乎特别敏感,反应非常糟糕。我刚刚花了整整一个小时等待进程终止,在此期间甚至鼠标光标都无法移动。
如何避免这种情况?
1) 禁用交换 => 我经常启动很多进程,然后这些进程就变成非活动进程。非活动进程应该移至交换。
2)购买 SSD => 太贵了
3)设置最大内存 ulimit => 但如果程序需要大量内存,则会失败。问题不在于它使用了太多内存,而是它抑制了其他进程
4) 将重要程序 (X11、bash、kill、top 等) 保留在内存中并且从不交换这些程序 => 可以做到吗?怎么做?也许只交换大型程序?
5)?
答案1
总结
简短回答
- 最简单:使用较新的 Linux 版本,其中 systemd-oomd 在 2020 年下半年成为 systemd 的一部分。
- 否则,请使用较小的交换分区,并避免内核通过从慢速存储而不是 RAM 运行进程来试图实现没有内存限制的谎言。
- 对于大型交换,传统的内核空间 OOM(内存不足管理器)没有及时采取行动。通常,它会根据虚拟内存进行计算,根据我过去的经验(使用 Ubuntu 16.04),它直到整个交换都填满后才会终止。因此,系统会出现抖动和爬行...
- 需要对 hibernate 进行大规模替换,并且仍使用未内置 systemd-oomd 的旧版本 systemd?
- 卡在了一些没有 cgroup2 的旧东西上?
- 尝试/有问题:设置一些 ulimit(例如检查,并可能使用中的选项
ulimit -v
设置硬限制或软限制)。这曾经运行得很好,但由于 WebKit 引入,许多 gnome 应用程序现在需要无限的地址空间并且无法运行!as
limits.conf
gigacage
- 尝试/有问题:过度承诺策略和比例是尝试管理和缓解这种情况的另一种方法(例如
sysctl vm.overcommit_memory
,,sysctl vm.overcommit_ratio
但这种方法对我来说不起作用。 - 困难/复杂:尝试将 cgroup 优先级应用于最重要的进程(例如 ssh),但这目前对于 cgroup v1 来说似乎很麻烦(cgroup v2 旨在使其更容易)...
- 尝试/有问题:设置一些 ulimit(例如检查,并可能使用中的选项
我还发现:
长答案和背景
到 2021 年,多项 Linux 内核改进(cgroups v2、psi、更好的页面写回节流)以及 systemd-oomd 已进入较新的发行版中。
请注意,许多 cgroup 资源控制改进和 systemd 对它的支持默认情况下并未被利用。对于桌面用例,它需要最终用户手动调整。
拥有全职开发团队的传统 Linux 发行版公司,例如 RedHat (CentOS/Fedora)、SUSE 或 Ubuntu (Debian),可能更关注服务器端用例,而不是桌面响应能力。也许 Android 生态系统更有可能将 Linux 拉向最终用户计算设备响应能力需求,但鉴于 Android 上使用的 init 不是 systemd,因此除了内核之外,没有太多的共享优势,因此它不会有太大帮助。此外,为 Android 打包的内核可能配置和构建方式不同。
一些有趣的补丁:
- 让后台写回不再糟糕习惯于某个问题
- 这阻止:连接写回限制补丁有助于避免后台和前台 IO 需求之间的 IO 争用。
- mm,oom:引入 oom reaper
因此,这不仅仅是糟糕的用户空间代码和发行版配置/默认值的问题 - 旧内核可以更好地处理这个问题,新内核也可以,尽管内核 OOM 仍然不理想。
除了改进内核 OOM 的努力之外,用户空间内存不足管理器的开发也在进行中systemd-oomd
。不幸的是,Ubuntu 20.04 LTS 并未包含它,因为它们发布的是旧版本的 systemd。请参阅Systemd 247 合并 Systemd-OOMD,以改进低内存/内存不足处理。
对已考虑的选项的评论
- 禁用交换
建议至少提供一个较小的交换分区(我们真的需要在现代系统上进行交换吗?禁用交换不仅可以防止交换出未使用的页面,还可能影响内核用于分配内存的默认启发式过量提交策略(Overcommit_memory =0 中的启发式方法是什么意思?),因为该启发式方法确实依赖于交换页面。没有交换,过量使用可能仍可在启发式 (0) 或始终 (1) 模式下工作,但没有交换和从不 (2) 过量使用策略的组合可能是一个糟糕的想法。因此在大多数情况下,没有交换可能会损害性能。
例如,考虑一个长时间运行的进程,它最初接触内存以进行一次性工作,但随后无法释放该内存并继续在后台运行。内核将不得不使用 RAM 来完成此操作,直到进程结束。没有任何交换,内核无法将其分页用于其他实际想要主动使用 RAM 的任务。还要考虑有多少开发人员很懒,在使用后没有明确释放内存。
- 设置最大内存 ulimit
它仅适用于每个进程,并且一个进程不应该请求比系统物理内存更多的内存,这可能是一个合理的假设!因此,在仍然慷慨设置的情况下,阻止一个疯狂的进程触发抖动可能很有用。
- 将重要程序(X11、bash、kill、top 等)保存在内存中,并且永远不要交换它们
想法不错,但这些程序会占用它们不主动使用的内存。如果程序仅请求适量的内存,这可能是可以接受的。
systemd 232 发布添加了一些使之成为可能的选项:我认为可以使用“MemorySwapMax = 0”来防止像 ssh 这样的单元(服务)的任何内存被换出。
尽管如此,如果能够优先处理内存访问那就更好了。
OOM、ulimit 以及为了响应性而牺牲完整性
交换抖动(当内存工作集,即在给定的短时间内读取和写入的页面超出物理 RAM 时)将始终锁定存储 I/O - 没有任何内核魔法可以在不终止一两个进程的情况下拯救系统...
人们曾希望,Linux 内核 OOM 调整功能在较新的内核中能够识别工作集内存压力超过物理内存的情况,并在这种情况下识别和终止最合适的进程,即贪婪地使用最多内存的进程。“工作集”内存是进程依赖并频繁访问的活动/正在使用的内存。
传统内核 OOM 无法识别工作集超出物理内存 (RAM) 的情况,因此发生了抖动问题。大型交换分区使问题更加严重,因为看起来系统仍然有虚拟内存余量,而内核却愉快地过度提交并切断了更多内存请求,但工作集可能会溢出到交换分区,实际上试图将存储视为 RAM。
在服务器上,内核 OOM 似乎接受了抖动带来的性能损失,以换取坚定、缓慢、不丢失数据的权衡。在台式机上,权衡有所不同,用户宁愿丢失一点数据(牺牲进程)来保持响应。
这是关于 OOM 的一个很搞笑的比喻:oom_pardon,又名不要杀死我的 xlock
顺便说一句,OOMScoreAdjust
另一个 systemd 选项是帮助衡量和避免 OOM 终止进程,这被认为更为重要。
OOMD 现在已嵌入到 systemd 中,因为 systemd-oomd 可以将此问题从内核空间移到用户空间。
缓冲写回
“让后台写回不再糟糕“解释了 Linux 的“页面缓存”(块存储缓冲)在将缓存刷新到存储时如何导致一些问题。请求过多 RAM 的进程可能会迫使需要从页面缓存中释放内存,从而触发从内存到存储的大量页面写回。不受限制的写回会在 IO 上获得过多的优先级,并导致其他进程的 IO 等待(阻塞)。如果内核用完了页面缓存,则换出其他进程的内存(更多写入磁盘)将继续争夺 IO,从而继续 IO 争用并使其他进程无法访问存储。这不是导致系统抖动问题本身的原因,但它确实加剧了整体响应能力的下降。
值得庆幸的是,有了较新的内核,阻止:连接写回限制 应该限制页面缓存写回存储的影响。我认为这也适用于后台进程内存交换到存储。
ulimits 限制
ulimits 的一个问题是,限制适用于虚拟内存地址空间(这意味着结合物理空间和交换空间)。根据man limits.conf
:
rss maximum resident set size (KB) (Ignored in Linux 2.4.30 and higher)
因此,设置仅适用于物理 RAM 使用的 ulimit 看起来不再有用。因此
as address space limit (KB)
似乎是唯一受人尊敬的可调参数。
不幸的是,正如 WebKit/Gnome 的例子所详述的那样,如果虚拟地址空间分配有限,某些应用程序就无法运行。
cgroups 可以提供帮助
目前,它看起来很麻烦,但可以启用一些内核 cgroup 标志cgroup_enable=memory swapaccount=1
(例如在 grub 配置中),然后尝试使用 cgroup 内存控制器来限制内存使用。
cgroups 具有比‘ulimit’选项更高级的内存限制功能。CGroup v2注释暗示尝试改进 ulimits 的工作方式。
组合的内存+交换核算和限制被对交换空间的实际控制所取代。
CGroup 选项可以通过以下方式设置systemd 资源控制选项。例如:
- 记忆高
- 记忆力
其他有用的选项可能是
- 输入输出权重
- CPU份额
这些有一些缺点:
- 开销。Docker 文档简要提到了 1% 的额外内存使用和 10% 的性能下降(可能与内存分配操作有关 - 它并没有真正具体说明)。
- 不久前,Cgroup/systemd 内容经过了大量重新设计,因此上游的流动意味着 Linux 发行版供应商正在等待它先稳定下来。
在CGroup v2,他们认为这memory.high
应该是限制和管理进程组内存使用的一个好选择。然而,2015 年的这句话表明,监控内存压力情况需要做更多的工作:
需要测量内存压力(由于内存不足对工作负载的影响有多大),以确定工作负载是否需要更多内存;不幸的是,内存压力监控机制尚未实现。
2021 年,这项工作似乎已经完成。请参阅 Facebook 所做的一些工作和分析的参考资料:
由于 systemd 和 cgroup 用户空间工具很复杂,过去没有简单的方法来设置适当的内容,所以我没有进一步利用它。Ubuntu 的 cgroup 和 systemd 文档不太好。
最后,较新的桌面版本可能会利用 cgroups 和 systemd-oomd,以便在高内存压力下不会启动抖动。但是,未来可以做更多的工作来确保 ssh 和 X-Server/窗口管理器组件获得对 CPU、物理 RAM 和存储 IO 的更高优先级访问,并避免竞争不太重要的进程。内核的 CPU 和 I/O 优先级功能已经存在了一段时间。似乎缺少对物理 RAM 的优先访问,但现在已通过 cgroups v2 进行了修复。
对于 Ubuntu 20.04,CPU 和 IO 优先级根本没有设置。当我检查 systemd cgroup 限制、CPU 份额等时,据我所知,Ubuntu 16.04 甚至现在的 20.04 都没有嵌入任何预定义的优先级。例如,我运行:
systemctl show dev-mapper-Ubuntu\x2dswap.swap
我将其与 ssh、samba、gdm 和 nginx 的相同输出进行了比较。如果发生抖动,GUI 和远程管理控制台等重要内容必须与所有其他进程平等竞争。
我跑了:
grep -r MemoryMax /etc/systemd/system /usr/lib/systemd/system/
并且发现没有单元默认对单元文件应用任何 MemoryMax 限制。因此默认情况下没有任何限制,并且 systemd cgroup 内存限制配置需要由系统管理员明确配置。
我在 16GB RAM 系统上的内存限制示例
我想启用休眠功能,因此需要一个大的交换分区。因此尝试使用 ulimits 等来缓解这种情况。
限制
警告: 硬性限制不再起作用!
我* hard as 16777216
设定了/etc/security/limits.d/mem.conf
不允许任何单个进程请求超过物理可能内存量的内存。我不会完全阻止系统抖动,但如果没有,只要有一个进程贪婪地使用内存,或者发生内存泄漏,就会导致系统抖动。例如,我见过有人gnome-contacts
在执行一些普通操作(例如从 Exchange 服务器更新全局地址列表)时消耗 8GB 以上的内存……
从 可以看出ulimit -S -v
,许多发行版都将此硬限制和软限制设置为“无限制”,因为从理论上讲,一个进程最终可能会请求大量内存,但只会主动使用其中的一部分,并且会愉快地运行,认为它被分配了 24GB 的 RAM,而系统只有 16GB。当内核拒绝其贪婪的推测性内存请求时,上述硬限制将导致原本可以正常运行的进程中止。
但是,它还会捕获诸如 gnome 联系人之类的疯狂事物,并且不会导致我的桌面响应能力下降,而是会出现“可用内存不足”的错误:
设置地址空间(虚拟内存)的 ulimit 的复杂性
不幸的是,一些开发人员喜欢假装虚拟内存是一种无限的资源,而对虚拟内存设置 ulimit 可能会破坏某些应用程序。例如,WebKit(一些 gnome 应用程序依赖于它来集成 web 内容)添加了一项gigacage
安全功能,该功能会尝试分配大量虚拟内存,并FATAL: Could not allocate gigacage memory
在出现错误时发出警告Make sure you have not set a virtual memory limit
。这种变通方法GIGACAGE_ENABLED=no
放弃了安全优势,但同样,不允许限制虚拟内存分配也会放弃安全功能(例如可以防止拒绝服务的资源控制)。具有讽刺意味的是,在 gigacage 和 gnome 开发人员之间,他们似乎忘记了限制内存分配本身就是一种安全控制。遗憾的是,我注意到依赖 gigacage 的 gnome 应用程序不会费心明确请求更高的限制,因此在这种情况下即使是软限制也会破坏一切。根据Debian webkit 团队新闻:
如果基于 webkit 的应用程序的最大虚拟内存大小受到限制(例如使用 ulimit -v),则可能无法运行
公平地说,如果内核能够更好地根据驻留内存使用情况而不是虚拟内存来拒绝内存分配,那么假装虚拟内存是无限的就不会那么危险了。
过度承诺
如果您希望拒绝应用程序的内存访问并且希望停止过度提交,请使用以下命令来测试您的系统在高内存压力下的行为。
就我而言,默认的提交比率是:
$ sysctl vm.overcommit_ratio
vm.overcommit_ratio = 50
但只有在更改策略以禁用过度承诺并应用比例时,它才会完全生效
sudo sysctl -w vm.overcommit_memory=2
该比率意味着总共只能分配 24GB 内存(16GB RAM*0.5 + 16GB SWAP)。所以我可能永远不会看到 OOM 出现,并且实际上不太可能有进程不断访问交换中的内存。但我也可能会牺牲整体系统效率。
这将导致许多应用程序崩溃,因为开发人员通常不能妥善处理拒绝内存分配请求的操作系统。它权衡了偶尔因抖动而导致长时间锁定的风险(硬重置后丢失所有工作),以及各种应用程序更频繁崩溃的风险。在我的测试中,它没有多大帮助,因为当系统面临内存压力并且无法分配内存时,桌面本身就会崩溃。但是,至少控制台和 SSH 仍然可以工作。
VM 过量使用内存如何工作有更多信息。
我选择恢复为默认设置,sudo sysctl -w vm.overcommit_memory=0
因为整个桌面图形堆栈和其中的应用程序仍然会崩溃。
结论
- 较新的 systemd-oomd 应该可以使事情变得更好,并在物理内存压力过高之前终止贪婪的进程。
- 传统内核 OOM 效果不佳,因为它仅在物理和交换内存空间已耗尽。
- ulimits 和其他限制过度提交的方法对于桌面软件来说效果并不好,因为许多应用程序请求过大的内存空间(例如使用 WebKit 的应用程序)或者在内核拒绝内存请求时无法正常处理内存不足异常。