前言
运维工程师最怕半夜被叫起来:服务器内存使用率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 availableMem: 31Gi 28Gi 1.2Gi 128Mi 2.1Gi 4.5GiSwap: 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 <PID># 用以下脚本记录增长曲线(每30秒采样)while true; do ps -o pid,rss -p <PID> >> /tmp/mem_leak.logsleep 30done
第四步:用pmap查看进程的内存映射
# 查看进程内存映射详情pmap -x <PID> | sort -k3 -rn | head -20# 输出解读:# Address:虚拟地址# Kbytes:虚拟内存大小(RSS是实际物理占用)# RSS:实际使用的物理内存# Mode:内存映射类型([anon]匿名映射 / [stack]栈 / 文件映射)# Mapping:映射的文件或库# 如果看到某个anon段持续增长且不释放,基本可以判断泄漏就在那里
第五步:gdb现场调试(生产慎用)
# 用gdb attach到可疑进程,dump堆内存(需要root)gdb -p <PID>(gdb) dump memory /tmp/heap_dump.bin 0x<heap_start> 0x<heap_end>(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/<PID>/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.confgroup 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=15echo "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 ]; thenecho "【4. Swap】 未配置"else swappct=$(( swapused * 100 / swaptotal ))echo "【4. Swap使用率】 ${swappct}%" [ $swappct -gt 50 ] && echo "⚠️ Swap使用率过高,建议增加物理内存或优化应用"fiecho ""# 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分钟就发现苗头,这才是真正的治未病。
希望本文的教程对你有所帮助。如有疑问或需要专业技术支持,可通过以下方式联系我们:易云城IT服务,您身边的IT专家。