Skip to content

内存与负载排查 ​

free 显示内存"用完了"往往是正常的,load 高也不一定是 CPU 问题。这一节把这两个最常被误读的指标讲清楚。

一、free -h 怎么读 ​

bash
free -h
text
               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完全空闲
sharedtmpfs / 共享内存
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/meminfo
text
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:0kB

anon-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/loadavg
text
 23:10:11 up 1 day, 4:51,  load average: 12.41, 9.88, 7.62
12.41 9.88 7.62 2/1842 33120

load = 处于 R(可运行)和 D(不可中断睡眠)状态的 task 数,1/5/15 分钟指数平均。

/proc/loadavg 最后两个数:2/1842 是当前运行/总线程数,33120 是最近创建的 PID。

与核数比较 ​

bash
nproc
text
4
load含义
< 4有余量
≈ 4跑满
4~8有排队,但可接受
> 8明显过载

load 高但 CPU 不高 ​

这是最容易误判的情况:

bash
vmstat 1 3
text
 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  0

r=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 -2
text
 si   so    bi    bo
  0    0   120  2048
 84  120   240 18432

si(swap in)/ so(swap out)非 0 就是内存不足,在拿磁盘当内存用。

bash
free -h
swapon --show
cat /proc/PID/status | grep VmSwap
text
               total        used        free
Swap:          4.0Gi       3.8Gi       200Mi

NAME      TYPE      SIZE USED PRIO
/swapfile file        4G 3.8G   -2

Swap 用到 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 -10
text
    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 filebeat

RSS 包含共享库,多个进程会重复计算同一份共享内存。更准确的做法:

bash
# 需要装 smem
smem -tk -c 'pid user rss pss uss command' | tail -20
指标含义
RSS驻留集,含共享
PSS按共享比例分摊,最公平
USS进程独占,进程死后能释放的量

单个进程的详细分布:

bash
cat /proc/28441/smaps_rollup
text
Rss:             2012340 kB
Pss:             1820312 kB
Pss_Anon:        1750200 kB
Pss_File:          70112 kB
Pss_Shmem:             0 kB

Java 进程的内存构成 ​

bash
jcmd 28441 GC.heap_info
text
 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
done

Java 用 jmap 看堆:

bash
jmap -histo:live 28441 | head -20
text
 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=8

b=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.jar

Java 吃了 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 killerdmesg -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、碎片