返回首页

Linux 内存管理深潜 — Buffer/Cache/Page Cache/Slab/回收/OOM 全链路

📅 创建于 2026-05-13 🔄 更新于 2026-05-19 📝 929 字

Linux 内存管理深潜

"内存又满了,是不是泄漏了?"—— Linux 内核会主动把空闲内存拿去做 Buffer 和 Cache,这不是泄漏,这是特性。关键是分清"已用内存"和"真正被应用程序占用的内存"。

核心概念

Buffer 和 Cache 的本质区别

概念 作用 /proc/meminfo 字段 释放条件
Buffer 块设备元数据缓存(superblock、inode bitmap 等),加速磁盘元数据操作 Buffers 内存紧张时可回收
Page Cache 文件页缓存,把磁盘文件内容缓存到内存,加速读写 Cached 干净页直接回收,脏页需回写后回收
Slab 内核对象缓存(dentry、inode、网络缓冲区等) SReclaimable / SUnreclaimable 可回收部分自动释放

Linux 和 Windows 内存管理有本质区别:Windows 显示"已用内存"不含缓存,Linux 的 free 列是真正空闲的,available 才是应用可用的。

free -h 解读

$ free -h
              total     used      free    shared  buff/cache   available
Mem:           31Gi     8.2Gi     2.1Gi    1.3Gi      21Gi        22Gi
Swap:         8.0Gi     1.2Gi     6.8Gi
字段 含义 关键判断
used 应用程序 + 内核占用(不含可回收缓存) 持续增长 → 可能泄漏
free 完全未使用的内存 低不一定有问题
buff/cache Buffer + Page Cache + Slab 可回收 ✅ 正常行为,会用空闲内存
available 应用实际可申请的内存(含可回收缓存) ← 这才是真正的可用量

⚠️ 不要看 free 列,要看 available 列。 free 低但 available 高 → 系统健康,只是缓存用得多。

/proc/meminfo 关键字段

$ cat /proc/meminfo
MemTotal:       32768000 kB    # 总物理内存
MemFree:         2200000 kB    # 完全空闲
MemAvailable:   23100000 kB    # 可用(含可回收)
Buffers:          520000 kB    # 块设备元数据缓存
Cached:         16500000 kB    # 文件页缓存
SwapCached:       120000 kB    # 曾经换出又换回的页
Active:          8500000 kB    # 活跃页(最近被访问)
Inactive:        9200000 kB    # 不活跃页(优先回收)
Dirty:             32000 kB    # 等待回写的脏页
Writeback:             0 kB    # 正在回写的页
Slab:             780000 kB    # Slab 总大小
SReclaimable:     520000 kB    # 可回收 Slab(dentry、inode 缓存)
SUnreclaimable:   260000 kB    # 不可回收 Slab
KernelStack:       45000 kB    # 内核栈
PageTables:        62000 kB    # 页表

快速诊断:

  • Dirty 持续 > 几百 MB → IO 瓶颈,脏页来不及回写
  • SUnreclaimable 持续增长 → 可能内核内存泄漏
  • MemAvailable 持续下降 → 真实内存压力

Page Cache 机制

Page Cache 是 Linux 最核心的 IO 性能优化之一。读写文件时,内核先把数据缓存在 Page Cache 中,后续访问直接从内存命中:

# 查看 Page Cache 命中率
sar -B 1 5
# pgpgin/s: 每秒从磁盘读入的页数
# pgpgout/s: 每秒写出到磁盘的页数
# fault/s: 缺页次数
# majflt/s: 主缺页(需读磁盘)← 高说明 Cache 命中不足

手动释放 Page Cache(仅测试/紧急情况):

echo 1 > /proc/sys/vm/drop_caches  # 释放 Page Cache
echo 2 > /proc/sys/vm/drop_caches  # 释放 Slab(dentry/inode)
echo 3 > /proc/sys/vm/drop_caches  # 释放两者

⚠️ 生产环境不要轻易 drop_caches,会瞬间增加磁盘 IO 压力。

Slab 内存

Slab 是内核对象缓存,slabtop 可以查看哪些内核结构占用最多:

slabtop -s c | head -20
常见 Slab 对象 含义 异常信号
dentry 目录项缓存 频繁 ls / find 大量小文件导致暴涨
inode_cache inode 缓存 同上
kmalloc-* 通用内核内存分配 持续增长 → 内核模块泄漏
proc_inode_cache /proc inode 大量 /proc 读取
TCP TCP 缓冲区 大量网络连接

SUnreclaimable 持续增长是最常见的内核内存泄漏信号。

内存回收三级机制

第一级:kswapd(后台回收)

内核线程 kswapd 在内存低于 watermark 时被唤醒,异步回收干净页、压缩内存、换出匿名页。

# 查看 kswapd 活动
ps aux | grep kswapd
cat /proc/vmstat | grep -E "kswapd|pgsteal|pgscan"

第二级:Direct Reclaim(直接回收)

当内存分配速度超过 kswapd 回收速度,申请内存的进程自己被阻塞去回收内存。这是性能拐点——应用线程突然变慢。

# 查看 direct reclaim 活动
cat /proc/vmstat | grep allocstall
# allocstall_dma / allocstall_normal 持续增长 → 内存严重不足

第三级:OOM Killer(终极手段)

前两级都跟不上时,OOM Killer 按 oom_score 选择进程杀掉。

# 查看 OOM 事件
dmesg | grep -i "out of memory\|killed process"

# 查看进程 oom_score(越高越容易被杀)
cat /proc/<PID>/oom_score

# 保护关键进程(-1000 完全保护,1000 优先被杀)
echo -1000 > /proc/<PID>/oom_score_adj

cgroup v2 内存控制

在容器/K8s 环境中,cgroup v2 提供精细的内存控制:

# 查看 cgroup 内存限制
cat /sys/fs/cgroup/memory.max        # 硬限制(字节),超过触发 OOM
cat /sys/fs/cgroup/memory.high        # 软限制,超过触发回收但不杀进程
cat /sys/fs/cgroup/memory.low         # 保护阈值,低于此值尽量不回收

# cgroup 内存压力
cat /sys/fs/cgroup/memory.pressure
# some avg10=25.30 avg60=18.72    # 至少一个任务因内存阻塞
# full avg10=5.10 avg60=3.20      # 所有任务都因内存阻塞

关键指标:

  • memory.current — 当前使用量
  • memory.stat — 详细统计(含 Page Cache、Swap、Kernel 等)
  • nr_throttled 不为 0 → 内存被 cgroup 限流

内存泄漏排查工具链

快速判断:真泄漏还是正常缓存?

# 1. 看 available,不是 free
free -h | grep Mem

# 2. available 持续下降 + free 已很低 → 真实压力
watch -n 2 'free -h | grep Mem'

# 3. 检查是否 Swap 在增长
vmstat 1 5 | awk 'NR>2{print "si:",$7,"so:",$8}'

定位泄漏进程

# 按 RSS(物理内存)排序
ps aux --sort=-%mem | head -20

# 更精确:PSS(按共享比例分摊)
smem -s pss -r | head -20

# 观察进程内存趋势
pidstat -r -p <PID> 1 10

深入分析

# /proc/<PID>/smaps — 进程内存映射详情
cat /proc/<PID>/smaps | grep -E "^(Rss|Pss|Shared|Private)"

# /proc/<PID>/smaps_rollup — 汇总版
cat /proc/<PID>/smaps_rollup

# 查看内存映射区域
pmap -x <PID>

eBPF/bpftrace 追踪

# 追踪进程内存分配(谁在申请内存)
bpftrace -e 'tracepoint:kmem:kmalloc {
  @[comm] = count(); }'

# 追踪 OOM 事件
bpftrace -e 'kprobe:oom_kill_process {
  printf("OOM killer activated, killing %s pid %d\n", str(args->p->comm), args->p->pid); }'

# 追踪 Page Cache 回写延迟
bpftrace -e 'kprobe:balance_dirty_pages {
  @usecs = hist(nsecs / 1000); }'

Java/Go 应用专用

# Java:查看 JVM 内存(堆+元空间+Native)
jcmd <PID> VM.native_memory summary

# Go:pprof 内存分析
curl http://localhost:6060/debug/pprof/heap > heap.prof
go tool pprof -http=:8080 heap.prof

常见场景速查

现象 根因 排查入口
free 很低但 available 很高 Page Cache 正常占用 ✅ 正常,无需处理
available 持续下降 真实内存压力 ps 按 %MEM 排序
SUnreclaimable 持续增长 内核内存泄漏(dentry/inode 泄漏) slabtop -s c
Dirty 持续很大 IO 瓶颈,脏页来不及回写 iostat -x 检查磁盘
allocstall 持续增长 Direct Reclaim,性能拐点 扩容或降内存使用
Swap 持续抖动 物理内存不足 free -h + ps 排查
OOM Killer 触发 内存严重不足 dmesg 查看被杀进程

监控告警

# 内存可用率(最重要)
node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes < 0.2

# Swap 使用率
node_memory_SwapFree_bytes / node_memory_SwapTotal_bytes < 0.5

# Direct Reclaim 活动
rate(node_vmstat_allocstall[5m]) > 0

# OOM Kill 事件
increase(node_vmstat_oom_kill[5m]) > 0

# Slab 不可回收持续增长
deriv(node_memory_SUnreclaim_bytes[1h]) > 10485760  # > 10MB/hour

# cgroup 内存压力
container_memory_pressure > 20

关联页面

页面关联点
server-performance-four-dimensions服务器五维排查总纲(内存为其中一维)
redis-memory-optimizationRedis 内存优化(应用层内存管理)
linux-kernel-tuning-production内核参数调优(vm.swappiness/vm.min_free_kbytes)
linux-load-average-guideLoad Average 与 OOM/Swap 的关联排查
linux-disk-io-tuning磁盘 IO 调优(脏页回写与 Page Cache 关系)

jvm-container-oom-offheap-troubleshootingJVM 堆外内存(DirectByteBuffer/Metaspace/线程栈)导致容器 OOMKi

k8s-capacity-planning-qos-cost-optimizationK8s 容量规划方法论与成本优化 — 从流量到资源预算的完整框架,含 QoS 策略、弹性伸缩协同、落
linux-filesystem-directory-structure-guideLinux FHS 文件系统目录结构详解 — 含各目录用途、运维排查路径速查、磁盘分区方案
online-troubleshooting-checklist四维排查速查清单(CPU/磁盘/内存/网络 + Java 工具 jstack/jmap/jstat/tcpdump)