进程与内存:从 ps 到 OOM Killer
先分清三个数字
ps aux 输出的 VSZ 与 RSS 是最容易被误读的两列。
| 指标 |
含义 |
能否反映真实占用 |
| VSZ |
虚拟地址空间大小 |
否,包含未分配页 |
| RSS |
常驻物理内存 |
是,但含共享页 |
| PSS |
按比例分摊共享页后的内存 |
最准,需 /proc/<pid>/smaps |
一个 Go 服务 VSZ 显示 2GB 但 RSS 只有 80MB 是完全正常的——goroutine 栈、mmap 预留都会计入 VSZ。
# 按真实内存占用排序(排除自身)
ps -eo pid,ppid,rss,comm --sort=-rss | head -20
# 更精确:看进程的 PSS
awk '/^Pss:/ {sum+=$2} END {print sum/1024 " MB"}' /proc/<pid>/smaps
Page Cache 不是「浪费」
free 命令里 buff/cache 很大并不代表内存泄漏。
free -h
# total used free shared buff/cache available
# Mem: 15Gi 3.2Gi 680Mi 120Mi 11Gi 11Gi
关键看 available 而不是 free。Page Cache 是内核用来加速文件 IO 的,当应用需要内存时会自动回收。只有当 available 也接近耗尽,才是真正的内存压力。
什么时候会触发 OOM Killer
内核选择杀哪个进程,依据的是 oom_score,而 oom_score 主要由 进程的 RSS 占比 决定。
# 查看各进程的 OOM 分数
for p in /proc/[0-9]*; do
echo "$(cat $p/oom_score 2>/dev/null) ${p#/proc/}"
done | sort -rn | head -10
三个常见触发原因
- 真实内存超限:容器 cgroup 限额低于应用峰值。
- overcommit 策略激进:
vm.overcommit_memory = 1 允许超量分配,兑现时刻才失败。
- 内存碎片:物理内存总量够,但缺少连续大页。
cat /proc/sys/vm/overcommit_memory
# 0 启发式(默认) 1 总是允许 2 严格按比例
dmesg -T | grep -i "out of memory"
# 内核会打印被杀进程的名字和当时的访存情况
自我保护:给关键进程降低 OOM 优先级
# -1000 表示永不杀(谨慎使用),正值更容易被杀
echo -500 > /proc/<pid>/oom_score_adj
# systemd 托管的服务可以直接配置
# [Service]
# OOMScoreAdjust=-500
# MemoryMax=2G
排障顺序
遇到 OOM,建议按这个顺序走:
dmesg -T | grep -i oom —— 确认谁被杀了、什么时间。
free -h —— 看 available,判断是总量不足还是缓存问题。
cgroup 限额 —— 容器环境检查 memory.max。
/proc/<pid>/status 的 VmRSS 与 Threads —— 区分是内存泄漏还是线程爆炸。
- 应用自身的堆快照 —— 定位到底是哪块数据在膨胀。
小结
- 看内存先看
available,不要被 buff/cache 吓到。
- OOM 的选择依据是 RSS 占比,不是「谁最重要」。
- 容器场景下,cgroup 限额往往才是真正的天花板。