Ubuntu香港服务器频繁触发OOM Killer,如何从Swap使用率定位内存异常?
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 上升,说明系统正在发生交换抖动。

可以结合内存压力指标:
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。

对于容器环境,应检查容器级内存和 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 的触发边界。