适用场景
这篇文章适用于线上服务突然退出、容器被重启、systemd 日志里只有 Killed、监控显示内存瞬时打满,但应用日志没有明确异常栈的场景。常见环境包括物理机、云主机、Docker 容器和 Kubernetes 节点。
OOM Killer 的本质是内核在内存不可回收时主动选择进程终止,以保证系统还能继续运行。排查重点不是只把服务拉起来,而是确认是谁触发了内存压力、为什么会被选中、后续如何降低再次发生的概率。
现象描述
典型现象如下:
- 服务进程突然消失,应用日志中没有正常退出记录。
- systemd 服务状态显示
code=killed, signal=KILL或日志里出现Killed。 - Docker 容器状态出现
OOMKilled=true。 - Kubernetes Pod 状态为
OOMKilled,restartCount增加。 - 机器在故障时间点出现内存、swap、page cache 或 slab 异常变化。
先不要急着改参数,第一步应确认是否真的发生了 OOM。
可能原因
常见原因可以分为几类:
- 应用自身内存泄漏,例如缓存无限增长、批量查询一次性加载大结果集。
- 并发突增导致堆内存、连接池、队列或临时对象快速膨胀。
- 机器上多个进程争抢内存,目标服务只是被内核选中杀掉。
- 容器或 Pod 的 memory limit 设置过低,业务峰值超过限制。
- swap 关闭或过小,短时内存尖峰没有缓冲空间。
- JVM、Go、Python 等运行时内存参数与容器限制不匹配。
- 日志、压缩、备份、报表等离线任务在业务高峰期占用大量内存。
排查思路
推荐按下面顺序排查:
- 确认 OOM 事件是否存在。
- 找到被杀进程、触发时间和内核选择原因。
- 对比故障前后的内存、swap、进程占用和业务流量。
- 判断是整机 OOM、容器 OOM,还是 cgroup limit 触发。
- 给出短期止血和长期治理方案。
常用命令
查看内核 OOM 日志
journalctl -k --since "2026-07-12 00:00:00" | grep -Ei "out of memory|oom|killed process"
关键字段说明:
Out of memory:内核确认进入 OOM 处理。Killed process:被杀进程的 PID、进程名和内存占用。total-vm:进程虚拟内存大小,不等于实际物理内存。anon-rss:匿名页常驻内存,通常更接近应用实际占用。file-rss:文件映射占用。oom_score_adj:进程被 OOM Killer 选中的倾向,值越大越容易被杀。
如果系统没有持久化 journal,可以尝试:
dmesg -T | grep -Ei "out of memory|oom|killed process"
dmesg 里的日志可能会被覆盖,事故后应尽快采集。
查看 systemd 服务退出原因
systemctl status myapp.service -l
journalctl -u myapp.service --since "2026-07-12 00:00:00" --no-pager
重点看:
- 是否有
signal=KILL。 - 是否在同一时间点自动重启。
- 重启前是否有大量请求、任务调度或异常日志。
查看当前内存概况
free -h
cat /proc/meminfo | egrep "MemTotal|MemFree|MemAvailable|SwapTotal|SwapFree|Cached|Slab|SReclaimable|SUnreclaim"
判断时不要只看 free,更要看 MemAvailable。它表示系统在不明显影响运行的情况下还能分配给新进程的内存,比 MemFree 更有参考价值。
找出当前占用内存较高的进程
ps -eo pid,ppid,user,comm,%mem,rss,vsz,oom_score,oom_score_adj --sort=-rss | head -n 20
字段说明:
rss:实际驻留内存,单位 KB。vsz:虚拟内存,不适合作为唯一判断依据。oom_score:内核综合评估的 OOM 风险分。oom_score_adj:人为调整值,范围通常是-1000到1000。
如果某个非核心进程 oom_score_adj 很低,而核心业务进程较高,内核在 OOM 时可能优先杀业务进程,这需要调整服务策略。
Docker 容器排查
docker inspect my_container --format '{{.State.OOMKilled}} {{.State.ExitCode}} {{.State.FinishedAt}}'
docker stats --no-stream
docker inspect my_container --format 'Memory={{.HostConfig.Memory}} MemorySwap={{.HostConfig.MemorySwap}}'
如果 OOMKilled=true,说明容器被内存限制杀掉。HostConfig.Memory 为 0 表示没有设置容器内存上限;非 0 时单位是字节。
Kubernetes Pod 排查
kubectl describe pod my-pod -n my-namespace
kubectl get pod my-pod -n my-namespace -o jsonpath='{.status.containerStatuses[*].lastState.terminated.reason}{"\n"}'
kubectl top pod -n my-namespace
重点关注:
Last State: TerminatedReason: OOMKilledExit Code: 137Limits.memory和Requests.memory
Exit Code 137 通常表示进程收到 SIGKILL,在容器场景中经常与 OOMKilled 一起出现。
定位示例
假设故障时间是 10:32,用户反馈接口大量 502,服务随后自动恢复。先查看内核日志:
journalctl -k --since "2026-07-12 10:20:00" --until "2026-07-12 10:40:00" \
| grep -Ei "out of memory|killed process"
示例输出:
Out of memory: Killed process 18423 (python) total-vm:5242880kB, anon-rss:3145728kB, file-rss:1024kB, oom_score_adj:0
这说明 PID 18423 的 Python 进程被杀,匿名 RSS 约 3 GB。继续查业务日志:
journalctl -u order-api.service --since "2026-07-12 10:20:00" --until "2026-07-12 10:40:00" --no-pager
如果发现 10:30 开始执行全量导出任务,就要继续确认导出逻辑是否一次性把结果集加载到内存。例如 Python 中常见的风险写法:
rows = cursor.fetchall()
for row in rows:
write_csv(row)
更稳妥的方式是分页或流式处理:
while True:
rows = cursor.fetchmany(1000)
if not rows:
break
for row in rows:
write_csv(row)
关键点是让单批数据量可控,避免一次请求或一次任务把全部数据放进内存。
修复方案
短期止血
- 先恢复服务,并保留故障时间点的内核日志、应用日志和监控截图。
- 临时降低高风险任务的并发,例如导出、报表、批处理、图片处理。
- 对容器或 Pod 适当提高 memory limit,但不要只加内存后结束排查。
- 如果是整机内存不足,迁移非核心任务或扩容节点。
- 对业务入口增加限流,避免峰值流量把队列和对象堆满。
长期治理
- 为批处理任务改造成分页、流式、分片执行。
- 为缓存设置最大容量和过期策略,避免无限增长。
- 为核心服务设置合理的内存 requests 和 limits。
- 为 JVM、Go、Python 等运行时配置与容器限制匹配的内存参数。
- 将定时任务和在线服务隔离部署,避免互相抢内存。
- 建立 OOM 告警,出现
OOMKilled、Killed process或内存可用量过低时及时通知。
预防措施
监控指标
建议至少监控以下指标:
MemAvailable持续低于阈值。- swap 使用率和 swap in/out。
- 进程 RSS 增长速度。
- 容器
container_memory_working_set_bytes。 - Kubernetes Pod 重启次数和
OOMKilled事件。 - 业务队列长度、请求并发、批处理任务耗时。
Prometheus 中可以用类似规则发现容器内存逼近限制:
container_memory_working_set_bytes{container!="",pod!=""}
/
container_spec_memory_limit_bytes{container!="",pod!="",container_spec_memory_limit_bytes>0}
> 0.9
这个表达式表示容器工作集内存超过限制的 90%。如果持续触发,就要检查是否存在内存泄漏、容量不足或 limit 设置过低。
服务配置
systemd 服务可以根据重要程度调整 OOM 倾向:
[Service]
OOMScoreAdjust=-500
OOMScoreAdjust=-500 会降低进程被 OOM Killer 选中的概率,但不要把所有服务都设置成低值。更推荐保护核心入口服务,同时让可重试的离线任务保持默认或更高的 OOM 倾向。
在 Kubernetes 中,合理设置 requests 和 limits:
resources:
requests:
memory: "512Mi"
cpu: "500m"
limits:
memory: "1Gi"
cpu: "1"
requests 影响调度和资源保障,limits 是容器可使用的上限。limit 太低会频繁 OOMKilled,太高则可能让节点整体内存压力失控。
总结
OOM 排查要避免两个误区:一是只看应用日志,忽略内核日志;二是只加内存,不追踪触发内存增长的业务路径。完整链路应该是先确认 OOM 事件,再定位被杀进程和触发时间,随后结合业务流量、任务日志、容器限制和进程 RSS 判断根因。
治理上要同时处理容量、隔离、代码和告警。对于线上服务,分页处理、限制并发、合理设置容器内存、隔离批处理任务和监控 OOM 事件,通常比单纯扩容更可靠。
Discussion
评论