首页 / 内容指南 / 当前文章

systemd 服务被 OOM killed:MemoryMax、MemoryHigh、cgroup 与 OOMPolicy 排查

发布于 2026-08-26 · Content Fleet 编辑部

systemd 服务被 OOM killed:MemoryMax、MemoryHigh、cgroup 与 OOMPolicy 排查

先固定故障时间与 unit 结果

记录服务名、故障时间、主 PID、退出码、信号、Result、重启次数和当时 unit 属性。使用 systemctl status 查看摘要,再用 journalctl -u 读取故障前后的完整日志;同时检查 kernel journal,寻找 OOM victim、cgroup 路径和内存统计。

不要只看重启后的当前状态。Restart=always 可能几秒内把服务拉起,导致页面显示 active,但此前已经发生过 OOM。保存 incident 时间窗并按 boot ID 区分重启前后的日志。

区分全局 OOM 与 cgroup OOM

全局 OOM 表示系统整体无法满足内存分配,内核从更大范围选择 victim;cgroup OOM 则是某个控制组达到 memory.max 且无法回收,受害范围限制在该 cgroup 内。两者的修复方向不同。

从内核日志中的 cgroup 路径判断进程属于哪个 unit 或 slice,并读取相应 memory.events。不要因为 free 在事后显示充足就否定 OOM,进程被杀和缓存回收后内存已经释放。

MemoryMax 是硬上限

systemd 的 MemoryMax= 映射到 cgroup v2 的 memory.max。官方手册说明,当使用量无法保持在该限制之下时,会在 unit 内触发 OOM killer。它是最后一道防线,不应被当成平滑限速器。

检查服务自身、模板实例和所有父 slice 的值。有效上限是 unit 与祖先限制中最严格的一个,并受物理内存约束。只修改 service 的 drop-in 但父 slice 仍较小,实际限制不会升高。

MemoryHigh 用于节流与回收压力

MemoryHigh= 映射到 memory.high,超过后进程会受到强回收和明显节流,但必要时使用量可以越过。systemd 文档建议把它作为主要内存控制机制,再用 MemoryMax 做最终保护。

如果服务在被杀前延迟突然升高、CPU 花在 reclaim 上,可能先反复触及 MemoryHigh。读取 memory.events 中 high、max、oom 和 oom_kill 计数的变化,不要只采集一个当前 RSS 值。

读取有效属性而不是只看 unit 文件

配置可能来自发行版 unit、/etc/systemd/system drop-in、运行时属性、模板、slice 或容器管理器。使用 systemd 查询实际属性,包括 MemoryCurrent、MemoryPeak、MemoryHigh、MemoryMax、EffectiveMemoryHigh 和 EffectiveMemoryMax。

修改 drop-in 后执行 daemon reload,并确认新值已进入运行状态。某些资源限制对现有 cgroup 的行为需按版本和变更方式验证;关键服务应在维护窗口做受控重启和压力测试。

memory.events 给出边界证据

cgroup v2 的 memory.events 会记录 low、high、max、oom、oom_kill 等事件。max 增长表示内存即将越过硬边界并触发回收;oom 表示发生 cgroup OOM 条件;oom_kill 增长证明有进程被 OOM killer 杀死。

采集计数器差值而不是只看绝对值。重启 unit 或重建 cgroup 后文件可能重置;父层的 memory.events 还可能包含后代事件,应同时读取目标 cgroup 与祖先,避免把邻居服务的事件归错。

OOMPolicy 决定 unit 如何响应

OOMPolicy= 控制 systemd 对内核 OOM 杀进程事件的 unit 级响应。不同 service 类型和 systemd 版本的默认值、支持值可能不同,因此先查询当前主机手册和实际属性。

关键是区分“某个 worker 被杀”与“整个服务应视为失败”。如果主进程仍活着但 worker 已损坏,仅看 active 可能产生假健康。选择策略前要理解程序的进程模型,不能为了自动恢复就一律杀掉整个 cgroup。

systemd-oomd 是另一条处置路径

systemd-oomd 是用户态 OOM killer,可根据内存压力和 swap 使用情况选择 cgroup。它与内核因 memory.max 触发的 cgroup OOM 不是同一机制。

检查 systemd-oomd journal、oomctl 输出以及 unit/slice 的 ManagedOOM 或当前版本相应配置。如果 oomd 记录了被杀 cgroup,应按压力策略、swap 和工作集分析;不要只调 MemoryMax

容器限制可能位于 systemd 之外

服务若运行在 Docker、Kubernetes、nspawn 或虚拟化宿主中,外层容器也可能设置 memory limit。容器内 systemd 看到的物理内存、cgroup 命名空间和有效上限不一定等同宿主机。

同时检查 Pod/容器 limit、父 cgroup 和 unit 属性。Kubernetes 的 OOMKilled 状态、容器 runtime 事件与 systemd journal 要按时间对齐,不能只修改容器内 unit 文件。

统计匿名内存、缓存和内核内存

进程 RSS 不是 unit 的全部使用量。cgroup 会计可能包括多个子进程、page cache、共享内存和部分内核内存。读取 memory.currentmemory.peakmemory.stat,观察 anon、file、slab、sock 等构成。

若 file cache 升高,不代表一定泄漏;若 anon 持续随请求增长且不下降,才更像堆或缓存无界。结合应用指标、堆剖析和流量,而不是仅凭一次 ps 排名下结论。

Restart 会制造 OOM 循环

服务被杀后自动重启,若启动即加载大缓存或恢复全部队列,很快再次触限。频繁重启还会增加磁盘、CPU 和下游压力。检查 Restart=RestartSec=、启动限速与失败计数。

自动恢复只能作为止损。应设置退避和告警,让系统在持续 OOM 时进入可观察失败,而不是无限循环。不要用 SuccessExitStatus 把 SIGKILL 伪装成成功。

容量调整必须有依据

调高 MemoryMax 前,先计算稳态工作集、峰值并发、缓存上限和父 slice 预算。为单服务放开限制可能把局部 OOM 变成全局 OOM,伤害同机所有服务。

理想策略是先降低无界队列、缓存和批大小,再为合理峰值设置 MemoryHigh,最后留出 MemoryMax 保护。上线后监控 current/peak、high/max/oom 事件、重启率和业务延迟。

推荐排查顺序

1. 固定故障时间、boot ID、unit Result、信号和重启次数。

2. 对齐 service journal、kernel journal 和 systemd-oomd 日志。

3. 找出进程真实 cgroup 路径及所有父 slice。

4. 查询 MemoryCurrent、Peak、High、Max 与有效限制。

5. 比较目标和父 cgroup 的 memory.events 计数差值。

6. 读取 memory.stat,区分 anon、file、slab 与 socket 内存。

7. 检查容器或编排层的外部 memory limit。

8. 核对 OOMPolicy、Restart 和应用多进程健康语义。

9. 用受控负载复现并完成堆、缓存或队列分析。

10. 调整限制后验证不会把压力转移为全局 OOM。

常见错误

不要只看事后的 free -h;不要只改服务自身而忽略父 slice;不要把 systemd-oomd 与内核 cgroup OOM 混为一谈;不要无限提高 MemoryMax;也不要依赖快速重启掩盖持续增长的工作集。

常见问题

主机还有空闲内存,服务为什么仍被 OOM killed?

服务或父 slice 可能达到更小的 MemoryMax,从而在局部 cgroup 内触发 OOM。应检查有效限制和 memory.events

MemoryHigh 和 MemoryMax 有什么区别?

MemoryHigh 主要触发回收和节流,必要时可越过;MemoryMax 是硬保护边界,无法回收到限制内时会触发 OOM killer。

systemctl 显示 active 就代表没有 OOM 吗?

不代表。服务可能已被自动重启,或只有某个 worker 被杀而主进程仍活着。要检查历史 journal、Result、重启次数和应用健康。

提高 MemoryMax 能永久解决吗?

只有原限制确实低于合理峰值时才可能解决。若存在泄漏、无界缓存或并发失控,放大限制只会推迟下一次故障。

oom_kill 计数为零为何仍退出?

可能是全局 OOM、systemd-oomd、外层容器处置,或程序收到分配失败后自行退出。需要结合内核、oomd、runtime 与应用日志判断。

总结

systemd 服务 OOM 的根因可能位于 unit、父 slice、内核全局、systemd-oomd 或容器外层。通过有效限制、memory.events、内存构成和时间对齐,可以把“被杀”定位到具体边界,再决定修复泄漏、调整工作集还是重新分配资源,而不是盲目加内存。

来源资料

  • systemd.resource-control(5):https://www.man7.org/linux/man-pages/man5/systemd.resource-control.5.html
  • systemd.service(5):https://www.freedesktop.org/software/systemd/man/latest/systemd.service.html
  • systemd-oomd.service(8):https://www.freedesktop.org/software/systemd/man/latest/systemd-oomd.service.html
  • Linux kernel Control Group v2:https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v2.html