
服务器监控显示还有几 GB 可用内存,应用却突然退出,dmesg 或 journalctl 里出现 Out of memory: Killed process。这类故障经常发生在容器、systemd 服务或虚拟机里:进程看到的可用内存,可能只是宿主机的情况;真正限制它的还可能是 cgroup、服务级 MemoryMax、容器 limit 或瞬时内存峰值。
排查 OOM 时要把“谁杀了进程”“哪个内存边界先到”“峰值发生在什么时间”三件事分开确认。
先确认是内核 OOM,还是应用自己退出
先查当前启动周期的内核日志:
sudo journalctl -k -b --no-pager | grep -i -E 'out of memory|oom|killed process'
sudo dmesg -T | grep -i -E 'out of memory|oom|killed process'
如果系统使用 journald,journalctl -k -b 通常比只看应用日志更容易找到内核事件。日志里一般会给出被杀进程的 PID、进程名和当时的内存统计。记录事件时间,再向前回看应用、反向代理和容器日志,确认是哪一次请求或任务造成峰值。
应用返回 137 也只能说明进程收到了 SIGKILL 的常见结果,不能单独证明一定是 OOM。容器被手动停止、部署系统超时或其他进程发送 SIGKILL 也可能得到相同退出码。
主机有空闲内存,不代表服务没有触碰限制
先看主机层面的内存和 Swap:
free -h
cat /proc/meminfo | egrep 'MemTotal|MemAvailable|SwapTotal|SwapFree|CommitLimit|Committed_AS'
vmstat 1 5
MemAvailable 是内核估算的可用内存,不等于某个服务能使用的上限。SwapFree 也不能直接说明系统是否健康;如果 vmstat 中 si 和 so 持续增长,说明内存回收和换入换出正在影响系统,即使 OOM 尚未发生。
检查系统是否启用了过量提交:
sysctl vm.overcommit_memory vm.overcommit_ratio
这组参数影响内核如何判断内存分配是否可以承诺。不要为了绕过一次 OOM 就随意修改全局 overcommit 策略,先定位实际申请内存的进程和限制边界。
容器场景先看 limit、usage 和事件
查看容器的内存限制和当前状态:
docker stats --no-stream app
docker inspect -f '{{.HostConfig.Memory}} {{.HostConfig.MemorySwap}} {{.State.OOMKilled}} {{.State.ExitCode}}' app
docker events --since 30m --until "$(date -Is)" --filter container=app
# 需要持续监听时可去掉 --until,并在确认后按 Ctrl+C 停止
HostConfig.Memory 为 0 通常表示没有设置容器级内存上限,但这不代表容器不会受到宿主机、systemd 或编排平台限制。State.OOMKilled=true 是 Docker 记录的一个重要线索,但仍应和内核日志时间对应起来。
Compose 中常见的限制配置如下:
services:
app:
image: example/app:latest
mem_limit: 512m
memswap_limit: 1g
不同 Compose、Swarm 和 Kubernetes 配置的字段并不完全相同。部署前确认实际生效的配置,不要只看仓库里的 YAML:
docker compose config
docker inspect app --format '{{json .HostConfig}}'
不要用容器内 /proc/meminfo 是否与宿主机不同来判断 limit 是否生效。普通 Docker 容器可能仍显示宿主机内存视图;容器实际可用上限应以 cgroup 文件和 Docker/编排层配置为准。
直接读取 cgroup 的 memory.max 和 memory.current
现代 Linux 通常使用 cgroup v2。先确认挂载类型:
stat -fc %T /sys/fs/cgroup
cat /proc/self/cgroup
cgroup v2 下,当前服务所在组常见的指标包括:
PID=$(pgrep -n -f 'your-app-pattern')
CG=$(awk -F: '$1 == "0" {print $3}' /proc/$PID/cgroup)
CGROOT=/sys/fs/cgroup
for f in memory.current memory.max memory.events memory.stat; do
echo "--- $CGROOT$CG/$f"
sudo cat "$CGROOT$CG/$f"
done
?????/proc/$PID/cgroup ?????????? /sys/fs/cgroup??? namespace ????????? / ?????? cgroup ????????memory.current ???? cgroup ???????memory.max ??????? max ??????????????memory.events ?? high?max?oom ? oom_kill ???????????????????????????????? cgroup ???????? /sys/fs/cgroup ?????????????
memory.current 表示当前 cgroup 的内存使用量,memory.max 是硬限制;显示 max 时表示该层没有设置固定上限。memory.events 中的 high、max、oom 和 oom_kill 可以帮助判断是否触碰过限制。不同发行版、服务管理器和容器运行时的 cgroup 路径不同,不要把 /sys/fs/cgroup 下的固定目录写死到脚本里。
cgroup v1 的路径和文件名不同,常见指标是 memory.usage_in_bytes、memory.limit_in_bytes 和 memory.failcnt。先确认版本,再使用相应文件,避免把 v1 命令套到 v2 主机上。
systemd 服务也可能有自己的 MemoryMax
如果应用不是直接启动,而是由 systemd 管理,检查服务的资源属性:
systemctl show app.service -p MemoryCurrent -p MemoryMax -p MemoryHigh -p MemorySwapMax
systemctl status app.service --no-pager
MemoryMax 是硬上限,MemoryHigh 更像是回收压力边界。服务可能在整台服务器仍有空闲内存时,因为自己的 cgroup 限制触发 OOM。修改 unit 文件后要重新加载并确认实际值:
sudo systemctl daemon-reload
systemctl show app.service -p MemoryCurrent -p MemoryMax -p MemoryHigh
# ????????????????????
sudo systemctl restart app.service
如果服务由 Docker、systemd 和上层编排共同管理,实际限制可能是多层取更小的边界。记录每一层的限制,才能解释为什么监控图表和进程行为看起来不一致。
找到峰值,不要只看故障后的瞬时值
OOM 发生后,free -h 只能反映当前时刻,峰值可能已经过去。为应用和容器持续记录内存:
vmstat 10 # 按 Ctrl+C 停止
pidstat -r -p ALL 10 # 需要 sysstat,按 Ctrl+C 停止
容器环境可以周期性保存:
while sleep 10; do
date -Is
docker stats --no-stream --format '{{.Name}} {{.MemUsage}} {{.MemPerc}}'
done
同时记录请求量、队列长度、批处理文件数量和进程线程数。内存泄漏、缓存无界增长、并发突增和大文件一次性读入,曲线形状往往不同。只有把内存曲线和业务事件放到同一时间轴上,才能决定是限制配置不合理,还是应用确实需要降低峰值。
Swap 不是无限内存,也不是所有场景都该关闭
查看 Swap:
swapon --show
cat /proc/sys/vm/swappiness
Swap 可以缓冲一部分不活跃页,但会带来磁盘 I/O 延迟。数据库、低延迟 API 和容器平台对 Swap 的处理方式也不同。把 Swap 关闭后,峰值更容易直接触发 OOM;盲目扩大 Swap 则可能把故障变成持续抖动。应根据服务类型、磁盘性能和 cgroup 的 memory.swap.max 一起评估。
处理顺序
先用内核日志确认 OOM 事件和被杀进程,再检查主机 MemAvailable、Swap、容器或 systemd 的限制,最后读取 cgroup 的 memory.current、memory.max 和 memory.events。把故障时间与应用峰值、队列、批任务和日志放在同一时间线上。调整限制前先确认应用的实际峰值;调整后再用压力测试和真实业务流量验证,避免仅凭一张主机内存图做判断。如果日志中没有内核 OOM,但进程仍被 SIGKILL,检查 journalctl -u systemd-oomd,因为 systemd-oomd 也可能在内核 OOM 之前终止服务。




