内存与 OOM:谁吃掉了我的内存
「内存怎么只剩这么点了」——多数情况下是误读。但真出问题时,也需要能快速定位是谁。
一、free 怎么看
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大是正常的。
# 真实使用率应该这么算
free | awk '/Mem/ {printf "%.1f%%\n", ($3-$6)/$2*100}'
# 或者更准确
free | awk '/Mem/ {printf "%.1f%%\n", ($2-$7)/$2*100}'二、谁在用内存
# 按内存排序看进程
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 | 虚拟地址空间(含未分配、共享库,虚高) |
ps -eo pid,rss,vsz,comm
# 常见现象:Java 进程 VSZ 好几个 G,RSS 只有几百 M —— 正常三、内核与缓存占用
ps 里看不到的内存去哪了:
# 系统级明细
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
内存真的耗尽时,内核会挑一个进程杀掉:
# 看有没有发生过 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),分高的先死:
# 看当前分数
cat /proc/<PID>/oom_score
# 手动调整(-1000 表示永不杀死)
echo -100 | sudo tee /proc/<PID>/oom_score_adj
# systemd 服务里配置
# [Service]
# OOMScoreAdjust=-500保护关键服务(数据库、SSH)用
OOMScoreAdjust=-500;让不重要的进程先死就调高。
五、容器场景
# 看每个容器的内存
docker stats
docker stats --no-stream --format "{{.Name}}\t{{.MemUsage}}\t{{.MemPerc}}"
# 限制容器内存(很重要,防止一个容器拖垮整机)
docker run -m 512m --memory-swap 512m ...# compose 写法
services:
app:
image: myapp
deploy:
resources:
limits:
memory: 512Msystemd 服务也能限:
[Service]
MemoryMax=1G
MemoryHigh=800M # 软限制,超过会积极回收而不是直接杀六、swap 该不该用
| 场景 | 建议 |
|---|---|
| 桌面 / 开发机 | 开,防止卡死 |
| 数据库服务器 | 关或很小 + swappiness=1 |
| 小内存 VPS(1-2G) | 开一点(1G),避免 OOM |
# 看看 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 是性价比最高的「扩容」——比升级套餐便宜,能防住偶发的内存峰值。
七、内存泄漏怎么判断
# 定时记录某个进程的 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 持续单调增长、重启后回落 → 泄漏。
临时解决方案(重启不优雅但有效):
# systemd 服务:内存超限自动重启
[Service]
MemoryMax=800M
Restart=always
RestartSec=10八、排查流程
# 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
- 知道正常基线是多少(出问题时才有对比)
内存问题的第一步永远是分清「缓存占用」和「真实占用」。大多数告警是前者,虚惊一场。
