theme: default themeName: "默认主题" title: "Linux内存越用越少,是谁在偷吃?腾讯架构师教你用这套方法线"
前言
运维工程师最怕半夜被叫起来:服务器内存使用率99%,应用响应慢,SSH都卡了。登上去一看,`free -h`显示内存快没了,top扫一眼发现`something`进程占了大半内存,但当你`kill`掉那个进程后,内存并没有完全释放回来——这时候基本可以判断:你遇到的不是真正的内存泄漏,而是Linux内存管理的正常机制在"背锅"。
但也不排除真有泄漏的情况。问题是:怎么快速区分?怎么准确定位?
先搞清楚:Linux内存都去哪了
Linux的内存分配遵循一个核心原则:分配不等于使用。当你启动一个进程申请100MB内存,它可能实际只占了几MB的物理内存,剩余的虚拟地址空间只是"占着不用"。这叫惰性分配(Lazy Allocation),是操作系统提升内存利用率的标准做法。
所以`free`命令看到的结果需要会解读:
# 最基础的内存查看
free -h
更高信息量的版本,加上 -w(分Wide列显示)
free -wh
实时监控
watch -n 1 free -h
`free`输出的典型解读:
total used free shared buff/cache available
Mem: 31Gi 28Gi 1.2Gi 128Mi 2.1Gi 4.5Gi Swap: 8Gi 128Mi 7.9Gi
重点看`available`而不是`free`:`available`才是真正可供新进程使用的内存(包含可回收的cache),而`free`是彻底空闲的物理内存。Linux会把空闲内存拿来当缓存用,看起来少了但实际上随时可以释放。
场景一:可回收内存——"泄漏"的假象
症状:内存使用率很高,但`buff/cache`占用大量空间,`available`也正常,应用性能没有明显下降。 诊断:# 查看内存分配详情
cat /proc/meminfo grep -E "MemTotal MemFree MemAvailable Buffers Cached Active Inactive SwapCached"
重点关注以下指标:
Active(file) + Inactive(file) = 页面缓存(可回收)
SReclaimable = 可回收的slab内存
Shmem = 共享内存(通常计入cache)
slab内存(内核对象缓存)占用
slabtop -o head -20
或用命令:
cat /proc/slabinfo awk '{print $1, $3}' sort -k2 -rn head -15
处理方法:
# 手动触发内存回收(释放page cache)
sync && echo 3 > /proc/sys/vm/drop_caches
drop_caches的值含义:
1 = 只释放page cache
2 = 释放dentries和inode
3 = 释放page cache + dentries + inode(最彻底)
如果slab占用高,用以下命令排查哪些内核对象吃得多
perf stat -e kmem:* -a sleep 5 # 需要perf工具
⚠️ 生产环境谨慎使用`drop_caches`,可能瞬间增加系统负载。建议用`crond`定时在低峰期执行,并确保先`sync`。
场景二:真正的内存泄漏
症状:内存持续增长,`available`越来越小,`swap`开始被使用,即使内存空闲时也不回收。 诊断步骤:# 第一步:观察趋势,内存是否只增不减
pidstat -r 1 5 # 每秒采样,共5次,看RSS变化
第二步:定位高内存进程
ps aux --sort=-%mem head -20
第三步:确认泄漏速率
选一个可疑进程,记录它的内存使用
ps -o pid,rss,vsz,comm -p
用以下脚本记录增长曲线(每30秒采样)
while true; do ps -o pid,rss -p >> /tmp/mem_leak.log sleep 30 done
第四步:用pmap查看进程的内存映射
# 查看进程内存映射详情
pmap -x sort -k3 -rn head -20
输出解读:
Address:虚拟地址
Kbytes:虚拟内存大小(RSS是实际物理占用)
RSS:实际使用的物理内存
Mode:内存映射类型([anon]匿名映射 / [stack]栈 / 文件映射)
Mapping:映射的文件或库
如果看到某个anon段持续增长且不释放,基本可以判断泄漏就在那里
第五步:gdb现场调试(生产慎用)
# 用gdb attach到可疑进程,dump堆内存(需要root)
gdb -p (gdb) dump memory /tmp/heap_dump.bin 0x 0x (gdb) quit
用strings查看堆内容(可能找到泄漏线索)
strings /tmp/heap_dump.bin head -100
场景三:OOM Killer在作妖
症状:应用进程被系统莫名杀死,`dmesg`里出现大量`Out of memory`日志,Java/Python等语言进程频繁崩溃。 诊断:# 查看内核日志,看OOM Killer的判决记录
dmesg grep -i "out of memory"
dmesg grep -i "oom"
更友好的格式(需要安装):
oomkill -l # 如果系统有oomkiller-tools
查看每个进程的OOM评分(分数越高越容易被杀)
for f in /proc/*/oom_score; do
p=$(echo $f cut -d/ -f3)
name=$(cat /proc/$p/comm 2>/dev/null echo "N/A")
score=$(cat $f) echo "PID $p ($name): $score"
done sort -k4 -rn head -10
控制OOM行为:
# 设置进程永不被OOM Killer杀掉(关键进程用)
echo -1000 > /proc//oom_score_adj
有效范围:-1000(永不杀)到 +1000(优先杀)
或者在启动脚本里加:
mysqld_safe --oom_score_adj=-1000
为Java进程关闭OOM Killer自动杀死(不推荐,可能导致系统僵死)
echo 1 > /proc/sys/vm/overcommit_memory
设置内存分配策略为"永远允许超额分配"
预防OOM的措施:
# 1. 限制cgroup的内存上限(容器化环境)
编辑 /etc/cgconfig.conf
group memlimit { memory { memory.limit_in_bytes = 4G; memory.soft_limit_in_bytes = 2G; memory.swappiness = 10; } }
2. 调整swappiness(降低换出倾向)
默认60,改成10~30(SSD环境)
sysctl vm.swappiness=15 echo "vm.swappiness=15" >> /etc/sysctl.conf
3. 设置最小空闲内存阈值
sysctl vm.min_free_kbytes=524288 # 512MB预留 echo "vm.min_free_kbytes=524288" >> /etc/sysctl.conf
场景四:swap疯狂使用导致性能塌方
症状:内存并没有满,但swap分区被大量使用,系统开始卡顿。# 查看各进程的swap使用量
for f in /proc/*/status; do
pid=$(echo $f cut -d/ -f3)
name=$(cat /proc/$pid/comm 2>/dev/null echo "N/A")
swap=$(grep VmSwap $f 2>/dev/null awk '{print $2, $3}')
[ -n "$swap" ] && echo "PID $pid ($name): $swap"
done sort -k2 -rn head -10
swap使用高但物理内存未满的原因通常是:某些进程在早期申请了大量虚拟内存但实际使用不多,Linux会将这些"冷数据"换出到swap。常见于Java应用(默认堆大小偏大)和PostgreSQL(shared_buffers配置)。
优化建议:# 检查进程虚拟内存 vs 物理内存的比率
ps aux awk '{print $6,$11}' sort -rn head -20
RSS(物理内存)vs VSZ(虚拟内存)差距过大说明有大量未用虚拟空间
调低swappiness,减少swap使用
sysctl vm.swappiness=10
监控swap I/O,确认是否影响性能
iostat -x 1 5
如果 %swpcnt 持续 > 80%,说明swap成为了性能瓶颈
实战脚本:内存健康检查
#!/bin/bash
mem_health_check.sh — Linux内存健康检查脚本
echo "=== Linux 内存健康检查报告 ===" $(date) echo ""
基础信息
echo "【1. 内存概览】"
free -h grep -E "Mem Swap"
echo ""
available 是否健康
avail=$(free awk '/Mem:/ {print $7}')
total=$(free awk '/Mem:/ {print $2}')
pct=$(( (total - avail) * 100 / total )) echo "【2. 内存使用率】 ${pct}% (available: ${avail}KB)"
[ $pct -gt 90 ] && echo "⚠️ 内存使用率过高!" echo "✅ 内存使用率正常"
echo ""
page cache占比
cache=$(free awk '/Mem:/ {print $6}')
echo "【3. Page Cache】 ${cache}KB" [ $cache -gt $((total/2)) ] && echo "📋 page cache较高,可尝试释放:sync && echo 3 > /proc/sys/vm/drop_caches" echo ""
Swap使用率
swaptotal=$(free awk '/Swap:/ {print $2}')
swapused=$(free awk '/Swap:/ {print $3}')
if [ $swaptotal -eq 0 ]; then echo "【4. Swap】 未配置" else swappct=$(( swapused * 100 / swaptotal )) echo "【4. Swap使用率】 ${swappct}%" [ $swappct -gt 50 ] && echo "⚠️ Swap使用率过高,建议增加物理内存或优化应用" fi echo ""
top内存进程
echo "【5. Top 5 高内存进程】"
ps aux --sort=-%mem head -6 tail -5 awk '{printf " PID %s %s: %s%%\n", $2, $11, $4}'
echo ""
OOM记录
oomcount=$(dmesg grep -c "Out of memory" 2>/dev/null echo "0")
echo "【6. OOM Kill历史】 最近发生 $oomcount 次"
[ $oomcount -gt 0 ] && echo "最近一次OOM详情:" && dmesg grep "Out of memory" tail -1
结语
Linux内存问题的排查,核心在于区分正常机制和真实故障。page cache多不等于有问题,swap被使用也不一定需要加内存。但当你确认是内存泄漏时,`pmap`和`pidstat`是定位问题的两把瑞士军刀。
记住一个原则:监控先于诊断。把内存趋势图画出来,比出事后再去看`free`有用得多。`Prometheus + Grafana`的组合能让你在问题爆发前30分钟就发现苗头,这才是真正的治未病。
希望本文的教程对你有所帮助。如有疑问或需要专业技术支持,可通过以下方式联系我们:
📞 服务热线:13708730161 💬 微信:eyc1689 📧 邮箱:service@eycit.com
易云城IT服务,您身边的IT专家。