外观
内存与负载排查
free 显示内存"用完了"往往是正常的,load 高也不一定是 CPU 问题。这一节把这两个最常被误读的指标讲清楚。
一、free -h 怎么读
bash
free -htext
total used free shared buff/cache available
Mem: 15Gi 13Gi 512Mi 1.0Gi 1.8Gi 900Mi
Swap: 4.0Gi 1.2Gi 2.8Gi| 列 | 含义 |
|---|---|
total | 物理内存总量 |
used | 已用(含应用 + 内核) |
free | 完全空闲 |
shared | tmpfs / 共享内存 |
buff/cache | 页缓存 + 缓冲,可回收 |
available | 真正可分配给新应用的量 |
free 小是正常的
Linux 会用空闲内存缓存磁盘数据(buff/cache)。看到 free 512Mi / 15Gi 就以为内存不足,是最常见的误判。
判断标准只有一个:available 是否够。available 低于总量 10% 才需要警惕。
buff/cache 与 available 的差距:
buff/cache里有一部分(如 tmpfs、共享内存)不能回收。available已经扣除了不可回收部分,是内核给出的估算。
二、/proc/meminfo 关键字段
bash
grep -E 'MemTotal|MemFree|MemAvailable|Buffers|Cached|SwapTotal|SwapFree|Dirty|Writeback|Slab|SReclaimable|Committed_AS|CommitLimit' /proc/meminfotext
MemTotal: 16384200 kB
MemFree: 524288 kB
MemAvailable: 921600 kB
Buffers: 102400 kB
Cached: 1835008 kB
SwapTotal: 4194300 kB
SwapFree: 2936012 kB
Dirty: 8192 kB
Writeback: 0 kB
Slab: 412288 kB
SReclaimable: 310112 kB
Committed_AS: 24883200 kB
CommitLimit: 12386400 kB| 字段 | 含义 | 关注点 |
|---|---|---|
Dirty | 已修改但未写回磁盘的页 | 持续很高说明磁盘跟不上 |
Writeback | 正在写回 | 长期非 0 = IO 卡 |
Slab/SReclaimable | 内核对象缓存 | 可回收,但泄漏时也会涨 |
Committed_AS | 所有进程承诺使用的内存总和 | 超过 CommitLimit 说明 overcommit 已到极限 |
Committed_AS > CommitLimit 时,新的 malloc 可能失败(取决于 overcommit_memory 策略)。
三、OOM killer 日志
进程"无故消失"、容器反复重启,先查 OOM。
bash
dmesg -T | grep -i -E 'out of memory|killed process' | tail -5
journalctl -k --since '1 hour ago' | grep -i 'killed process'text
[Tue Oct 27 21:31:02 2026] java invoked oom-killer: gfp_mask=0x100cca(GFP_HIGHUSER_MOVABLE), order=0
[Tue Oct 27 21:31:02 2026] Out of memory: Killed process 31882 (java) total-vm:3214342kB, anon-rss:2089412kB, file-rss:0kBanon-rss 是进程真实占用的匿名内存。total-vm 很大不代表真用了(虚拟地址空间)。
谁会被杀:内核按 oom_score 排序,分数最高的先死。可以调:
bash
cat /proc/31882/oom_score
echo -900 > /proc/31882/oom_score_adj # 尽量别杀它systemd 服务用 OOMScoreAdjust=-900,容器用 --oom-score-adj。
手动调 oom_score_adj 不能替代修内存
它只是让内核优先杀别人。真正的问题是内存不够——加内存、调小堆、修泄漏。
四、load average 到底是什么
bash
uptime
cat /proc/loadavgtext
23:10:11 up 1 day, 4:51, load average: 12.41, 9.88, 7.62
12.41 9.88 7.62 2/1842 33120load = 处于 R(可运行)和 D(不可中断睡眠)状态的 task 数,1/5/15 分钟指数平均。
/proc/loadavg 最后两个数:2/1842 是当前运行/总线程数,33120 是最近创建的 PID。
与核数比较
bash
nproctext
4| load | 含义 |
|---|---|
| < 4 | 有余量 |
| ≈ 4 | 跑满 |
| 4~8 | 有排队,但可接受 |
| > 8 | 明显过载 |
load 高但 CPU 不高
这是最容易误判的情况:
bash
vmstat 1 3text
r b swpd free buff cache si so bi bo in cs us sy id wa st
1 12 0 184320 12800 512000 0 0 1024 2048 4200 8200 3 2 20 75 0r=1(没在等 CPU)、b=12(12 个进程卡在 D 状态)、wa=75%。
bash
ps -eo pid,stat,wchan:25,cmd | awk '$2 ~ /^D/'text
PID STAT WCHAN CMD
31882 D rpc_wait_bit_killable java -jar /app/app.jar
31890 D nfs_file_direct_read tar -czf /backup/app.tar.gz /data结论:NFS 卡住了,12 个进程在等它。这时候加 CPU 没有任何用。
五、swap 与 si/so
bash
vmstat 1 3 | tail -2text
si so bi bo
0 0 120 2048
84 120 240 18432si(swap in)/ so(swap out)非 0 就是内存不足,在拿磁盘当内存用。
bash
free -h
swapon --show
cat /proc/PID/status | grep VmSwaptext
total used free
Swap: 4.0Gi 3.8Gi 200Mi
NAME TYPE SIZE USED PRIO
/swapfile file 4G 3.8G -2Swap 用到 90% 时,系统会进入"抖动":频繁换页,CPU 大量时间在 IO,表现为整体卡死但 CPU 不高。
数据库/Redis 机器应关闭 swap
延迟会从微秒变毫秒。设置:
bash
sysctl -w vm.swappiness=1
# 临时
swapoff -a容器里用 --memory-swap 等于 --memory 禁用 swap。
六、找内存大户
bash
ps -eo pid,user,rss,vsz,%mem,cmd --sort=-rss | head -10text
PID USER RSS VSZ %MEM CMD
28441 app 2012340 4214320 12.4 java -jar /app/app.jar
29112 mysql 1312580 2143212 7.4 mysqld
30211 root 512400 812000 3.1 filebeatRSS 包含共享库,多个进程会重复计算同一份共享内存。更准确的做法:
bash
# 需要装 smem
smem -tk -c 'pid user rss pss uss command' | tail -20| 指标 | 含义 |
|---|---|
RSS | 驻留集,含共享 |
PSS | 按共享比例分摊,最公平 |
USS | 进程独占,进程死后能释放的量 |
单个进程的详细分布:
bash
cat /proc/28441/smaps_rolluptext
Rss: 2012340 kB
Pss: 1820312 kB
Pss_Anon: 1750200 kB
Pss_File: 70112 kB
Pss_Shmem: 0 kBJava 进程的内存构成
bash
jcmd 28441 GC.heap_infotext
PSYoungGen total 524288K, used 89124K
ParOldGen total 1048576K, used 1048576K
Metaspace used 148231K, committed 165888K堆 1.5G,但进程 RSS 2G,差的 500MB 是:
| 部分 | 说明 |
|---|---|
| Metaspace | 类元数据 |
| 线程栈 | -Xss × 线程数 |
| 直接内存 | Netty/NIO 的 DirectByteBuffer |
| JIT 代码缓存 | 编译后的机器码 |
| GC 自身结构 | G1 的 region 元数据 |
对比:
bash
jcmd 28441 VM.native_memory summary # 需要 -XX:NativeMemoryTracking=summary七、内存泄漏
RSS 持续单调上涨,重启后回落,就是泄漏。
bash
# 每分钟采样,看趋势
while true; do
echo "$(date +%T) $(ps -o rss= -p 28441)"
sleep 60
doneJava 用 jmap 看堆:
bash
jmap -histo:live 28441 | head -20text
num #instances #bytes class name (module)
-------------------------------------------------------
1: 2184012 104832576 [B (java.base@17.0.12)
2: 842113 26947616 java.lang.String (java.base@17.0.12)
3: 412003 13184096 java.util.HashMap$Node (java.base@17.0.12)[B(byte 数组)占了一半,通常是某个缓存/队列在无限增长。
非 Java 进程:
bash
pmap -x 28441 | tail -5
cat /proc/28441/status | grep -E 'VmRSS|VmSwap|Threads'八、overcommit
bash
cat /proc/sys/vm/overcommit_memory| 值 | 策略 |
|---|---|
0 | 启发式(默认),预估不会明显超卖就允许 |
1 | 总是允许(Redis 需要这个) |
2 | 严格,CommitLimit 以内才允许 |
Redis 必须设 1,否则 BGSAVE 的 fork 会失败:
bash
sysctl -w vm.overcommit_memory=1
echo 'vm.overcommit_memory = 1' > /etc/sysctl.d/99-redis.conf代价:malloc 更容易成功,但真正用到时可能触发 OOM。
九、一次完整案例
现象:机器"越来越卡",ssh 都要等。
bash
uptime
# load average: 18.42, 16.11, 12.88
nproc
# 4
vmstat 1 3
# r=0 b=16 si=124 so=842 wa=68 id=8b=16、si/so 非 0、wa 68% —— 内存不足导致 swap,swap 导致 IO 等待,IO 等待抬高 load。
bash
free -h
# Mem used 15Gi, available 200Mi; Swap 4Gi used 4Gi
ps -eo pid,rss,cmd --sort=-rss | head -5
# 28441 9.2g java -jar /app/app.jarJava 吃了 9.2G。而 -Xmx 只有 2G?
bash
jcmd 28441 GC.heap_info
# ParOldGen total 2097152K used 2011000K -> 堆 2G 正常
cat /proc/28441/smaps_rollup | grep -E 'Rss|Pss'
# Rss 9642340 kB堆 2G 但 RSS 9.6G → 6G 在堆外。
bash
ls -l /proc/28441/fd | wc -l
dmesg -T | grep -c 'killed process'fd 有 3 万个,每个 socket 缓冲区都算内存 → 连接泄漏导致堆外内存暴涨。
修复方向:连接池上限 + 泄漏的 close(),而不是调 JVM 参数。
十、踩过的坑
| 现象 | 根因 | 处置 |
|---|---|---|
| free 小就以为内存不够 | buff/cache 可回收 | 看 available |
| 进程被杀但没日志 | OOM killer | dmesg -T |
| load 高但 CPU 空闲 | D 状态进程 | ps -eo stat,wchan |
| 系统卡死但 CPU 不高 | 在 swap 抖动 | 关 swap,加内存 |
| Java RSS 远大于堆 | 堆外内存 | 看 Metaspace/线程数/直接内存 |
| RSS 一直涨 | 泄漏 | jmap -histo / pmap |
Committed_AS 超限 | overcommit 限制 | 调 overcommit_memory 或加内存 |
| 容器 OOM 但 host 内存充足 | cgroup 限制 | 调 --memory |
available 正常但 malloc 失败 | 分配大页/连续内存失败 | 查 vm.max_map_count、碎片 |