内存与 OOM:谁吃掉了我的内存

「内存怎么只剩这么点了」——多数情况下是误读。但真出问题时,也需要能快速定位是谁。

一、free 怎么看

bash
free -h
#               total        used        free      shared  buff/cache   available
# Mem:           3.8G        1.2G        210M         32M        2.4G        2.3G
# Swap:          1.0G          0B        1.0G
含义
total总内存
used已用(含 buff/cache)
free完全空闲
buff/cache内核缓存(可回收
available真正可用的,看这个

Linux 的原则是「空闲内存就是浪费内存」,会把用不完的内存拿去做磁盘缓存。所以 free 小、buff/cache 大是正常的

bash
# 真实使用率应该这么算
free | awk '/Mem/ {printf "%.1f%%\n", ($3-$6)/$2*100}'

# 或者更准确
free | awk '/Mem/ {printf "%.1f%%\n", ($2-$7)/$2*100}'

二、谁在用内存

bash
# 按内存排序看进程
top -o %MEM          # 或 top 后按 M
ps aux --sort=-%mem | head -10

# 只输出关键列(RSS 单位是 KB)
ps -eo pid,ppid,user,rss,pmem,comm --sort=-rss | head -15

# 更友好的交互式工具
btop
htop

# 按用户汇总
ps -eo user,rss --no-headers | awk '{a[$1]+=$2} END {for (u in a) printf "%-12s %.0f MB\n", u, a[u]/1024}'

# 按进程名汇总(找出同类进程的总占用)
ps -eo comm,rss --no-headers | awk '{a[$1]+=$2} END {for (c in a) printf "%-16s %.0f MB\n", c, a[u]/1024}'

RSS vs VSZ

指标含义
RSS实际占用的物理内存(看这个
VSZ虚拟地址空间(含未分配、共享库,虚高)
bash
ps -eo pid,rss,vsz,comm
# 常见现象:Java 进程 VSZ 好几个 G,RSS 只有几百 M —— 正常

三、内核与缓存占用

ps 里看不到的内存去哪了:

bash
# 系统级明细
cat /proc/meminfo

# 关键项
# MemTotal / MemFree / MemAvailable
# Buffers / Cached / SReclaimable(可回收的 slab)
# AnonPages(进程匿名内存)
# Shmem(共享内存 / tmpfs)
# KernelStack / PageTables

# slab 占用异常高时看这里
sudo slabtop -o | head -20

# tmpfs(/dev/shm、容器)也算内存
df -h -t tmpfs

常见误区:Cached 高不代表内存不够。真要释放可以 echo 3 > /proc/sys/vm/drop_caches,但这只会让系统变慢,别当成优化手段。

四、OOM Killer

内存真的耗尽时,内核会挑一个进程杀掉:

bash
# 看有没有发生过 OOM
sudo dmesg -T | grep -i "killed process"
sudo journalctl -k | grep -i "out of memory"

# 典型日志
# Out of memory: Killed process 1234 (node) total-vm:2048000kB, anon-rss:512000kB

谁会被杀

内核给每个进程算一个 oom_score(0-1000),分高的先死:

bash
# 看当前分数
cat /proc/<PID>/oom_score

# 手动调整(-1000 表示永不杀死)
echo -100 | sudo tee /proc/<PID>/oom_score_adj

# systemd 服务里配置
# [Service]
# OOMScoreAdjust=-500

保护关键服务(数据库、SSH)用 OOMScoreAdjust=-500;让不重要的进程先死就调高。

五、容器场景

bash
# 看每个容器的内存
docker stats
docker stats --no-stream --format "{{.Name}}\t{{.MemUsage}}\t{{.MemPerc}}"

# 限制容器内存(很重要,防止一个容器拖垮整机)
docker run -m 512m --memory-swap 512m ...
yaml
# compose 写法
services:
  app:
    image: myapp
    deploy:
      resources:
        limits:
          memory: 512M

systemd 服务也能限:

nginx
[Service]
MemoryMax=1G
MemoryHigh=800M        # 软限制,超过会积极回收而不是直接杀

六、swap 该不该用

场景建议
桌面 / 开发机开,防止卡死
数据库服务器关或很小 + swappiness=1
小内存 VPS(1-2G)开一点(1G),避免 OOM
bash
# 看看 swap 到底用没用
swapon --show
vmstat 1 5            # 看 si/so 列,非 0 说明在换页(性能会掉)

# 调整倾向(0-100,越小越倾向用物理内存)
sudo sysctl -w vm.swappiness=10
echo "vm.swappiness=10" | sudo tee -a /etc/sysctl.d/99-baize.conf

对 1G 内存的小 VPS,加 1G swap 是性价比最高的「扩容」——比升级套餐便宜,能防住偶发的内存峰值。

七、内存泄漏怎么判断

bash
# 定时记录某个进程的 RSS
while true; do
  echo "$(date +%T) $(ps -o rss= -p <PID>)"
  sleep 60
done

# 或者观察几小时
watch -n 60 "ps -o pid,rss,etime,comm -p <PID>"

判读:RSS 持续单调增长、重启后回落 → 泄漏。

临时解决方案(重启不优雅但有效):

nginx
# systemd 服务:内存超限自动重启
[Service]
MemoryMax=800M
Restart=always
RestartSec=10

八、排查流程

bash
# 1. 是不是真的不够(看 available)
free -h

# 2. 是谁
ps -eo pid,user,rss,pmem,comm --sort=-rss | head -15

# 3. 有没有 OOM
sudo dmesg -T | grep -i "killed"

# 4. 是不是缓慢泄漏
watch -n 60 "ps -o rss= -p <PID>"

# 5. 缓存/内核占用
cat /proc/meminfo | grep -E "Slab|PageTables|Shmem"

九、检查清单

  • available 判断而不是 free
  • 关键服务设了 OOMScoreAdjust
  • 容器/服务设了内存上限
  • 有内存使用率的监控告警
  • 小内存机器配了 swap
  • 知道正常基线是多少(出问题时才有对比)

内存问题的第一步永远是分清「缓存占用」和「真实占用」。大多数告警是前者,虚惊一场。