上一篇 下一篇 分享链接 返回 返回顶部

Ubuntu香港服务器频繁触发OOM Killer,如何从Swap使用率定位内存异常?

发布人:Minchunlin 发布时间:2026-10-05 08:16 阅读量:2

Ubuntu 香港服务器频繁触发 OOM Killer 时,不能只看“Swap 已使用多少”这一项。Swap 使用率反映的是交换空间中已经放出的页面数量,而 vmstat 的 si/so 才能说明当前是否正在发生交换读写;真正判断内存是否持续紧张,还要结合 MemAvailable、进程 RSS、内存压力 PSI、OOM 日志以及 systemd 或容器的 cgroup 限制。

建议按“先确认 OOM 事件,再看可用内存和交换活动,最后定位进程及限制”的顺序排查。Swap 使用率高但 si/so 长时间为零,可能只是历史上被换出的冷数据仍未换回;如果 MemAvailable 持续接近零、si/so 持续增长并伴随 OOM 日志,才更接近真实的主机内存不足。

导语与总体排查顺序配图

先确认是否真的发生了 OOM

排查过程中不要一开始就执行 swapoff -a、强制重启服务或删除 Swap 文件。这些操作可能改变现场,甚至在内存已经紧张时再次触发 OOM。先保存内核日志和当前资源状态。

检查内核与系统日志

在 Ubuntu 上优先查看最近 24 小时的内核日志:

sudo journalctl -k --since "24 hours ago" | \
  grep -Ei 'out of memory|oom-killer|killed process|memory cgroup out of memory'

如果系统没有通过 journald 保存完整内核日志,再查看当前内核环形缓冲区:

sudo dmesg -T | \
  grep -Ei 'out of memory|oom-killer|killed process|memory cgroup out of memory'

典型日志可能类似下面的示例,内容仅用于说明判断方式:

oom-kill:constraint=CONSTRAINT_NONE,nodemask=(null),cpuset=/,mems_allowed=0
Out of memory: Killed process 1842 (java) total-vm:12483200kB, anon-rss:7340032kB

如果出现 Out of memory、Killed process,说明内核 OOM Killer 已经选择并终止了进程。anon-rss 较大通常说明该进程持有较多匿名内存,例如堆、栈和运行时分配的内存。

另一类日志可能是:

Memory cgroup out of memory: Killed process 2961 (worker)

这表示触发点可能是某个 cgroup 的内存上限,而不是整台服务器的物理内存耗尽。此时即使主机仍有较多 MemAvailable,被限制的服务也可能被杀掉。

还要留意以下情况:

  • 只有应用日志中出现进程退出,没有内核 OOM 日志,可能是应用自身异常退出、systemd 管理动作或其他监控组件执行了重启。
  • 日志中出现 memory cgroup out of memory,应优先检查服务、容器或任务的内存限制。
  • 出现 systemd-oomd 相关日志时,不要直接把它等同于内核 OOM Killer。两者都可能终止进程,但触发机制和处理位置不同。

可以补充检查 systemd-oomd 是否正在运行:

systemctl is-active systemd-oomd
sudo journalctl -u systemd-oomd --since "24 hours ago"

第二步:同时查看可用内存、Swap 状态和交换活动

使用 free 判断真正可用的内存

free -h

重点关注 available,而不是单独关注 free:

               total        used        free      shared  buff/cache   available
Mem:             16Gi        14Gi       180Mi       420Mi        1.8Gi        1.1Gi
Swap:           4096Mi       3.2Gi       896Mi

在现代 Ubuntu 中,free 通常按“总内存减去可用内存”计算 used。Linux 会主动利用空闲内存作为文件缓存,因此 free 较小并不等于内存已经耗尽;buff/cache 中一部分内容可以在需要时回收。

判断时可以遵循以下原则:

  • MemAvailable 较低,且随着时间继续下降,说明系统可立即分配的内存正在减少。
  • buff/cache 较大但 MemAvailable 仍然充足,通常不应直接清理缓存。
  • Swap 已使用较多,但 MemAvailable 充足且交换活动为零,可能是之前的冷页面被换出后一直没有重新访问。
  • MemAvailable 很低、Swap 使用率继续上升,同时应用响应变慢,通常说明已经进入内存回收压力阶段。

Swap 使用率可以按下面的方式理解:

Swap 使用率 =(SwapTotal - SwapFree)÷ SwapTotal × 100%

可以从 /proc/meminfo 读取原始数据:

grep -E 'MemTotal|MemAvailable|MemFree|Buffers|Cached|SReclaimable|Shmem|SwapTotal|SwapFree|CommitLimit|Committed_AS' /proc/meminfo

其中:

  • MemAvailable:内核估算的、不触发明显交换的可用内存,是判断主机压力的主要指标。
  • Cached:文件页缓存,不等于不可回收的进程内存。
  • SReclaimable:可回收的内核 Slab,数值较大时可以进一步检查内核对象缓存。
  • Shmem:共享内存和 tmpfs 使用的内存,可能被 /dev/shm 或应用共享内存占用。
  • SwapTotal、SwapFree:用于计算当前 Swap 占用。
  • Committed_AS 和 CommitLimit:用于观察内核承诺分配情况,但不能单独作为 OOM 的判定依据。

查看 Swap 是什么类型以及是否已经启用

swapon --show --bytes
cat /proc/swaps
lsblk -o NAME,TYPE,SIZE,FSTYPE,MOUNTPOINTS

正常情况下,swapon --show 至少应显示一个交换设备或交换文件,例如:

NAME      TYPE      SIZE USED PRIO
/swapfile file 4294967296 3.2G   -2

如果没有任何输出,说明当前没有启用 Swap。此时需要区分两种情况:

  • 服务器确实没有配置 Swap,内存突发时更容易直接进入 OOM。
  • 存在 Swap 文件或分区,但没有通过 swapon 启用,或者 /etc/fstab 配置未生效。

检查启动配置:

grep -nE '[[:space:]]swap[[:space:]]' /etc/fstab

不要仅根据 Swap 百分比决定是否扩容。一个 4 GiB 的 Swap 使用 80%,和一个 64 GiB 的 Swap 使用 80%,代表的换出数据量完全不同;还必须结合服务器物理内存、进程工作集和当前交换读写速度。

使用 vmstat 判断当前是否在持续换入换出

vmstat 1 10

重点看 si 和 so 两列:

  • si:从 Swap 读入内存的速率。
  • so:从内存写入 Swap 的速率。
  • r:等待运行的任务数量。
  • wa:等待 I/O 的 CPU 时间比例。

如果 si、so 只在采样开始时短暂出现,随后回到零,可能只是一次性内存回收或冷页面调入。如果两列在多个采样周期内持续有明显数值,同时 MemAvailable 很低、wa 上升,说明系统正在发生交换抖动。

第二步:使用 vmstat 判断交换活动配图

可以结合内存压力指标:

cat /proc/pressure/memory

输出通常类似:

some avg10=3.20 avg60=1.10 avg300=0.40 total=...
full avg10=0.80 avg60=0.20 avg300=0.05 total=...

some 表示至少有一部分任务因内存资源等待,full 表示一段时间内所有任务都受到内存压力影响。这里的百分比是压力时间占比,不是内存使用率。持续升高通常意味着系统已经出现可感知的内存竞争。

第三步:由进程 RSS 定位内存占用者

确认主机确实存在内存压力后,再定位具体进程。不要把 VSZ 或虚拟地址空间大小直接当成物理内存占用,优先看 RSS。

ps -eo pid,ppid,user,%mem,rss,vsz,stat,etime,cmd --sort=-rss | head -n 20

字段含义如下:

  • RSS:当前驻留在物理内存中的页面,单位通常为 KiB。
  • VSZ:进程申请或映射的虚拟地址空间,可能远大于实际使用量。
  • %MEM:RSS 占物理内存的比例。
  • STAT:进程状态,持续处于 D 状态时还应关注不可中断 I/O 等因素。

如果已经从 OOM 日志获得 PID,可以查看该进程当前内存明细:

PID=1842
sudo grep -E 'Name|State|VmPeak|VmSize|VmRSS|VmSwap|RssAnon|RssFile|RssShmem' \
  "/proc/${PID}/status"

也可以检查 OOM 选择权重:

sudo sh -c "printf 'oom_score: '; cat /proc/${PID}/oom_score; \
printf 'oom_score_adj: '; cat /proc/${PID}/oom_score_adj"

VmRSS 较大表示当前驻留内存多,VmSwap 较大表示该进程有部分页面已经被换出。需要注意,某个进程被 OOM Killer 终止后,其 PID 可能已经不存在,不能因为当前进程列表中找不到它就认为日志无效。

区分匿名内存、文件缓存和共享内存

多个进程可能共享同一份动态库或文件映射,单纯累加 RSS 会重复计算。对于疑似占用者,可以查看 smaps_rollup:

PID=1842
if [ -r "/proc/${PID}/smaps_rollup" ]; then
  sudo awk '/^(Rss|Pss|Pss_Anon|Pss_File|Pss_Shmem|SwapPss):/ {print}' \
    "/proc/${PID}/smaps_rollup"
else
  echo "当前内核未提供 smaps_rollup"
fi

其中:

  • Pss 会按共享比例分摊内存,比 RSS 更适合比较多个进程。
  • Pss_Anon 较大,通常指向应用堆、运行时对象或匿名缓存。
  • Pss_File 较大,可能是文件映射或共享库。
  • Pss_Shmem 较大,应进一步检查共享内存和 tmpfs。
  • SwapPss 可帮助确认该进程实际有多少比例的共享页面处于 Swap。

如果系统已安装 smem,也可以使用:

sudo smem -rtk | head -n 20

如果没有安装,不必为了临时排查强行安装工具,ps、/proc/PID/status 和 smaps_rollup 通常已经足够。

连续采样比单次排名更有价值。可以每 30 秒保存一次重点数据:

while true; do
  date
  free -h
  vmstat 1 2 | tail -n 1
  ps -eo pid,comm,rss,%mem --sort=-rss | head -n 8
  echo "-----"
  sleep 30
done

如果某个进程的 RSS 随时间单调增长,即使业务流量没有明显变化,也应优先检查内存泄漏、缓存上限、连接未释放和任务并发数。如果 RSS 相对稳定,但并发任务突然增加后整体内存快速上升,更可能是容量峰值问题。

第四步:检查 cgroup 和服务级内存限制

“主机还有内存但进程被 OOM 杀掉”是排查中最容易误判的一类。systemd 服务、容器和部分任务会受到独立的 cgroup 限制。

以 systemd 服务为例,将 app.service 替换为实际服务名:

SERVICE=app.service

systemctl show "$SERVICE" \
  -p ControlGroup \
  -p MemoryCurrent \
  -p MemoryHigh \
  -p MemoryMax \
  -p MemorySwapMax \
  -p OOMPolicy \
  -p OOMScoreAdjust

重点含义:

  • MemoryCurrent:该服务当前使用量。
  • MemoryHigh:超过后可能触发回收或节流,不一定立即杀进程。
  • MemoryMax:硬限制,超过后可能触发该 cgroup 内的 OOM。
  • MemorySwapMax:该服务可使用的 Swap 上限。
  • OOMScoreAdjust:影响内核选择进程的倾向,不是内存上限。
  • ControlGroup:该服务对应的 cgroup 路径。

如果系统使用 cgroup v2,还可以读取该服务的事件计数:

CG=$(systemctl show "$SERVICE" -p ControlGroup --value)

if [ -f "/sys/fs/cgroup${CG}/memory.events" ]; then
  sudo cat "/sys/fs/cgroup${CG}/memory.events"
  echo "-----"
  sudo cat "/sys/fs/cgroup${CG}/memory.current"
  echo "-----"
  sudo cat "/sys/fs/cgroup${CG}/memory.max"
  echo "-----"
  sudo cat "/sys/fs/cgroup${CG}/memory.swap.max"
else
  echo "未找到对应的 cgroup v2 文件,请检查服务名称或系统 cgroup 版本"
fi

如果 memory.events 中的 oom 或 oom_kill 持续增加,而整机 MemAvailable 仍然较高,说明应调整服务的资源边界或降低其自身并发,而不是盲目扩大整机 Swap。

第四步:检查 cgroup 和服务级内存限制配图

对于容器环境,应检查容器级内存和 Swap 限制。常用的观察命令是:

docker stats --no-stream

如果确认是容器限制导致,再查看具体配置:

docker inspect CONTAINER_NAME \
  --format 'Memory={{.HostConfig.Memory}} MemorySwap={{.HostConfig.MemorySwap}}'

容器内部看到的内存上限不一定等于宿主机的物理内存。此时需要同时核对宿主机 OOM 日志、容器事件和容器自身的进程 RSS。

根据结果区分不同类型的异常

下面的组合比单个指标更有判断价值:

观察结果更可能的含义处理方向
MemAvailable 持续很低,si/so 持续有值,内核日志出现 OOM整机真实内存压力定位 RSS/PSS 最大进程,降低并发、修复泄漏,并评估增加内存或 Swap
Swap 使用率较高,但 si/so 长时间为零,MemAvailable 仍充足历史冷页面仍留在 Swap,不一定是当前故障继续观察,不要在高负载时强制 swapoff
MemAvailable 充足,但 memory.events 中 oom_kill 增加cgroup 或服务级限制触发检查 MemoryMax、MemorySwapMax 和容器限制
某进程 RSS/PSS 持续增长,Swap 也随之增长进程泄漏、缓存无上限或任务堆积先限制增长源,安排有序重启,再分析应用内存生命周期
buff/cache 较大,但 MemAvailable 正常,si/so 为零正常文件缓存占用通常不需要清理缓存
Shmem 较大,/dev/shm 使用量持续增长共享内存或 tmpfs 占用异常检查共享内存对象、临时文件和应用清理逻辑
OOM 日志缺失,但 systemd-oomd 日志有终止记录用户态内存压力管理器提前采取动作检查 PSI、systemd-oomd 配置和被终止的服务

不要使用 sync; echo 3 | sudo tee /proc/sys/vm/drop_caches 作为常规修复手段。它只能暂时丢弃部分文件缓存,不能释放应用匿名内存,还可能造成后续磁盘读取增加,无法解决进程泄漏或 cgroup OOM。

修复顺序:先处理占用源,再调整 Swap

1. 先降低异常进程的增长速度

如果已经确认某个服务不断增加 RSS,优先从应用配置和任务模型处理:

  • 限制并发任务数和连接数。
  • 为内存缓存设置容量或过期时间。
  • 检查批量任务是否一次性读取过大的数据集。
  • 检查日志处理、消息消费和文件解析是否存在积压。
  • 对长期运行进程建立定期 RSS、PSS 和重启次数记录。

临时重启服务只能释放当前占用,不能证明问题已经修复。执行前应保存服务状态和日志,并确认重启对业务连接的影响:

sudo systemctl status app.service
sudo journalctl -u app.service --since "2 hours ago" > /tmp/app-service-before-restart.log

如果服务支持无损配置重载,优先使用服务自身支持的 reload;只有确认需要重新创建进程时才执行 restart。重启前应安排维护窗口或确认业务具备重试能力:

sudo systemctl restart app.service
sudo systemctl status app.service --no-pager

重启后的回滚不是简单地再次重启,而是恢复原配置并重新启动服务。因此涉及配置修改时,先备份配置文件,确认服务配置检查命令通过,再进行变更。

2. 修正过低的 cgroup 内存上限

如果 MemoryMax 或容器内存限制低于服务的正常工作集,应根据采样结果重新规划上限,不要直接设置成无限制。

一般可以先设置 MemoryHigh 作为软边界,并给 MemoryMax 留出应用峰值和运行时开销。但具体数值要来自一段高峰期采样,不能仅按进程启动后的初始 RSS 估算。

systemd 服务的临时配置可以通过 drop-in 管理:

sudo systemctl cat app.service
sudo systemctl edit app.service

在编辑器中按实际容量填写,例如:

[Service]
MemoryHigh=3G
MemoryMax=4G
MemorySwapMax=1G

这只是配置格式示例,不代表所有服务都应使用相同数值。MemoryMax 设得过低会把整机 OOM 变成服务内部 OOM;MemorySwapMax 设为零则可能让服务无法使用 Swap。

修改后执行:

sudo systemctl daemon-reload
sudo systemctl restart app.service
systemctl show app.service -p MemoryCurrent -p MemoryHigh -p MemoryMax -p MemorySwapMax

如果变更导致服务无法启动或频繁被限制,可恢复变更前的 drop-in。执行 systemctl revert 前应确认该服务没有其他需要保留的管理员覆盖配置:

sudo systemctl revert app.service
sudo systemctl daemon-reload
sudo systemctl restart app.service

3. 在磁盘容量允许时补充 Swap

Swap 的作用是吸收短时内存峰值、保存不活跃页面,为系统争取处理时间;它不能替代物理内存,也不能解决持续增长的内存泄漏。

先检查磁盘余量和根文件系统类型:

df -h /
findmnt -no FSTYPE,TARGET /
swapon --show

如果根文件系统有足够空间,且确认是适合普通 Swap 文件的 ext4 或 xfs,可以创建 Swap 文件。以下示例创建 4 GiB,实际大小应根据峰值内存和磁盘余量评估:

SWAPFILE=/swapfile
SIZE_GB=4

df -h /
sudo cp -a /etc/fstab "/etc/fstab.bak.$(date +%F-%H%M%S)"

sudo fallocate -l "${SIZE_GB}G" "$SWAPFILE"
sudo chmod 600 "$SWAPFILE"
sudo mkswap "$SWAPFILE"
sudo swapon "$SWAPFILE"

if ! sudo grep -Eq "^${SWAPFILE}[[:space:]]+none[[:space:]]+swap" /etc/fstab; then
  echo "${SWAPFILE} none swap sw 0 0" | sudo tee -a /etc/fstab
fi

swapon --show
free -h

如果 fallocate 创建的文件无法启用,可以删除该文件后改用连续写入方式创建;但只有在确认文件路径不是重要数据文件时才执行:

sudo swapoff "$SWAPFILE" 2>/dev/null || true
sudo rm -f "$SWAPFILE"

sudo dd if=/dev/zero of="$SWAPFILE" bs=1M count=4096 status=progress
sudo chmod 600 "$SWAPFILE"
sudo mkswap "$SWAPFILE"
sudo swapon "$SWAPFILE"

上述 rm 会永久删除指定路径,执行前必须确认变量和路径正确。对于 btrfs 等文件系统,不要直接套用普通 Swap 文件命令,应先确认该文件系统对 Swap 文件的特殊要求。已有 Swap 分区时,也不需要为了提高使用率而重新分区;先确认当前分区是否已启用以及容量是否满足需求。

4. 谨慎调整 vm.swappiness

查看当前值:

sysctl vm.swappiness

vm.swappiness 控制内核在回收匿名页和文件页之间的倾向,不是 Swap 开关。将它设为 0 也不能从根本上保证永不使用 Swap;当系统面临分配压力时,内核仍可能进行必要的交换。

对延迟敏感、但又保留少量 Swap 作为缓冲的服务,可以把 10 至 30 作为测试范围,而不是直接照搬固定值。例如:

sudo cp -a /etc/sysctl.d/99-memory.conf \
  "/etc/sysctl.d/99-memory.conf.bak.$(date +%F-%H%M%S)" 2>/dev/null || true

printf '%s\n' 'vm.swappiness = 20' | sudo tee /etc/sysctl.d/99-memory.conf
sudo sysctl --system
sysctl vm.swappiness

调整后必须重新观察 vmstat、应用延迟和 OOM 日志。如果交换读写更频繁或业务延迟升高,应恢复变更前的值,而不是继续降低或升高参数。删除新增配置文件并重新加载系统配置,可作为回滚方式:

sudo rm -f /etc/sysctl.d/99-memory.conf
sudo sysctl --system

如果该文件原本就存在,回滚时应使用前面保存的备份恢复,不要直接删除原有管理员配置。

OOM Killer 策略如何调整

OOM Killer 会综合进程内存占用、oom_score_adj 等因素选择牺牲对象。它的目标是让系统恢复可运行状态,不是保证某个业务进程一定存活。

因此不建议把所有关键服务都设置为不可被杀,也不建议直接对所有进程写入 oom_score_adj=-1000。这样做可能导致内核无法选择合适的进程,最终让整机卡死或触发更严重的故障。

先查看服务当前设置:

systemctl show app.service -p OOMScoreAdjust

只有在已经明确服务角色、依赖关系和资源占用后,才考虑通过 systemd drop-in 对单个关键服务设置适度的负值:

sudo systemctl edit critical.service

示例:

[Service]
OOMScoreAdjust=-300

该设置只是降低被优先选择的概率,不是绝对保护;如果服务本身持续泄漏,保护它可能会把 OOM 风险转移给其他服务。应用配置后需要重启服务才能让进程继承新设置:

sudo systemctl daemon-reload
sudo systemctl restart critical.service
systemctl show critical.service -p OOMScoreAdjust

如果发现策略导致其他非关键服务频繁被杀,应撤销 drop-in 并恢复原先的 OOM 选择策略。OOM 评分调整只能作为最后的资源隔离手段,不能代替增加容量、限制并发和修复泄漏。

修复后的验证方法

添加 Swap、调整服务限制或处理异常进程后,至少覆盖一次原先容易触发故障的业务高峰。不要因为 free -h 中 Swap 使用量暂时下降,就立即判定问题已经解决。

立即验证配置是否生效

free -h
swapon --show
grep -E 'MemAvailable|SwapTotal|SwapFree' /proc/meminfo
sysctl vm.swappiness

检查服务或 cgroup:

systemctl show app.service \
  -p MemoryCurrent \
  -p MemoryHigh \
  -p MemoryMax \
  -p MemorySwapMax \
  -p OOMScoreAdjust

确认 Swap 文件在重启后仍会自动启用时,可以在维护窗口验证 /etc/fstab 配置;不要在内存压力期间执行完整重启作为唯一验证手段。

观察交换活动和压力趋势

vmstat 1 10
cat /proc/pressure/memory
sudo journalctl -k --since "30 minutes ago" | \
  grep -Ei 'out of memory|oom-killer|killed process|memory cgroup out of memory'

较理想的结果是:

  • MemAvailable 在业务高峰后仍有余量,没有持续向零靠近。
  • si/so 只在突发峰值时短暂出现,未持续产生大量交换读写。
  • Swap 使用量不再单调增长,或者增长后能够稳定。
  • PSI 的 some、full 不再持续升高。
  • 内核日志和 cgroup 的 oom_kill 在观察窗口内不再增加。
  • 重点进程的 RSS/PSS 在高峰后回落或保持在可解释范围内。

参考上,持续低于总内存约 10% 的 MemAvailable、持续非零的 si/so,或内存 PSI 在多个采样窗口明显升高,都值得继续调查;这些数值不是所有业务都适用的硬阈值,应结合应用延迟和历史基线判断。

建立简单的持续采样

如果问题不是每分钟都出现,可以把关键指标写入临时日志,等待下一次高峰:

while true; do
  {
    date '+%F %T'
    free -h
    vmstat 1 2 | tail -n 1
    swapon --show
    echo "memory.pressure:"
    cat /proc/pressure/memory
    ps -eo pid,comm,rss,%mem --sort=-rss | head -n 10
    echo "===="
  } >> /var/tmp/memory-watch.log 2>&1
  sleep 60
done

当 OOM 再次发生时,把日志时间与 journalctl -k 中的时间对齐:如果 Swap 使用率上升但 si/so 不动,重点放在历史换出页面和进程工作集;如果 si/so、PSI、RSS 同时上升,重点放在真实内存压力;如果整机指标正常而 cgroup 事件增加,则应回到服务级限制和容器配置排查。这样才能避免把“Swap 已使用”误判成唯一根因,也能在不盲目扩容的情况下确定 OOM Killer 的触发边界。

目录结构
全文