Linux 内存、NUMA与OOM生产排障完整指南
Linux 内存问题经常被一句“内存占满了”概括,但 used 可能是可回收页缓存,也可能是进程匿名页、Slab、共享内存、HugePage或Cgroup限制。排障必须区分系统总内存、进程视角和容器限制,并沿分配、回收、交换和OOM路径定位。
一、理解内存组成
物理内存
├── 进程匿名页:Heap、Stack
├── 文件页:Page Cache、mmap
├── 内核:Slab、页表、网络缓冲
├── 共享内存
├── HugeTLB/THP
└── 可用、回收中与不可回收
free中的available比单纯free更有意义,它估算不触发明显Swap情况下可提供给新应用的内存。
二、快速基线
free -h
vmstat 1
cat /proc/meminfo
ps -eo pid,ppid,comm,rss,vsz,%mem --sort=-rss | head -30
slabtop -o
同时记录:
- MemAvailable;
- Swap使用和换入换出;
- Active/Inactive匿名页与文件页;
- Dirty/Writeback;
- Slab/SReclaimable/SUnreclaim;
- PageTables;
- HugePages;
- PSI Memory;
- OOM和内核日志。
三、RSS、PSS、USS与VSZ
- VSZ:虚拟地址空间,不代表实际物理占用;
- RSS:驻留物理页,共享页会在多个进程重复计算;
- PSS:共享页按使用者分摊,更适合汇总;
- USS:进程独占的物理页。
cat /proc/<pid>/status
cat /proc/<pid>/smaps_rollup
smem -tk
不要把所有进程RSS简单相加判断物理内存,因为共享库和共享内存会重复统计。
四、Page Cache不是浪费
Linux使用空闲内存缓存文件,应用需要时可回收。页缓存高而MemAvailable充足通常是正常现象。
危险信号:
- MemAvailable持续下降;
- 直接回收增加;
- major fault升高;
- Swap持续换入换出;
- PSI full持续;
- 应用分配延迟;
- OOM日志。
不要定时执行 drop_caches 作为“优化”。这会丢失有价值缓存并制造后续I/O洪峰。
五、匿名内存与泄漏
进程Heap、JVM、数据库Buffer、解释器对象通常属于匿名内存。
pmap -x <pid>
grep -E 'Rss|Pss|Private|Anonymous|Swap' /proc/<pid>/smaps_rollup
判断泄漏要看随业务负载归一化后的长期趋势,而不是一次快照。确认:
- 内存是否在请求结束后回落;
- 对象数量是否增长;
- allocator是否保留arena;
- 线程数是否增长;
- 缓存是否有上限;
- 文件映射是否增加。
六、Slab与内核内存
slabtop -o
cat /proc/slabinfo
Slab增长可能来自dentry、inode、网络连接跟踪、文件句柄或内核模块。SReclaimable可能回收,SUnreclaim更需关注。
排查:
- 大量小文件和目录扫描;
- 容器层和挂载;
- conntrack;
- Socket缓冲;
- 内核驱动泄漏;
- 删除但仍持有的对象。
七、Swap的正确认识
Swap不是绝对禁用或绝对启用。数据库和低延迟系统常限制或关闭Swap,通用服务器可用少量Swap吸收冷页,但持续swap-in/out表示压力。
swapon --show
vmstat 1
cat /proc/<pid>/status | grep VmSwap
关注 si/so 速率、存储延迟和PSI。降低swappiness不能解决真实内存不足。
八、内存回收与抖动
内核先回收文件页和可回收Slab,压力更大时扫描匿名页并Swap。Direct Reclaim会让应用线程亲自回收,造成延迟尖峰。
grep -E 'pgscan|pgsteal|allocstall|compact' /proc/vmstat
cat /proc/pressure/memory
PSI区分some和full:full表示所有非空闲任务都因资源压力阻塞,通常对服务延迟影响更明显。
九、OOM Killer
journalctl -k | grep -Ei 'out of memory|oom-killer|killed process'
dmesg -T | grep -Ei 'oom|killed process'
保存:
- 被杀进程;
- OOM触发上下文;
- 系统/节点总内存;
- Cgroup限制;
- 进程RSS/Swap;
oom_score_adj;- 当时工作负载。
重启被杀进程只能恢复服务,不能解释为何触发OOM。
十、Cgroup与容器OOM
容器可能在宿主机仍有大量空闲内存时被Cgroup OOM。
Cgroup v2示例:
cat /sys/fs/cgroup/<group>/memory.current
cat /sys/fs/cgroup/<group>/memory.max
cat /sys/fs/cgroup/<group>/memory.events
cat /sys/fs/cgroup/<group>/memory.stat
关注 oom、oom_kill、high 和 max。Kubernetes中还要区分容器limit、Pod QoS、节点MemoryPressure和驱逐。
十一、内存请求与限制
限制过低导致频繁OOM,过高则降低调度密度并掩盖泄漏。通过历史P95/P99、峰值、启动和故障场景设置请求与限制。
Java、Go、Node.js等运行时需要感知容器限制。JVM Heap不是进程全部内存,还包括Metaspace、Direct Buffer、线程栈、JIT和Native库。
十二、NUMA基础
多路服务器把CPU和内存划分为NUMA节点,访问远端内存延迟更高。
lscpu
numactl --hardware
numastat
numastat -p <pid>
大型数据库、虚拟化和GPU工作负载尤其需要关注CPU、内存和PCIe设备拓扑。
十三、NUMA失衡
症状:
- 某节点内存耗尽,其他节点仍有空闲;
- numa_miss/foreign升高;
- 远端访问多;
- 延迟在扩容CPU后反而升高;
- 进程跨节点频繁迁移。
治理:
- CPU和内存亲和性一起设置;
- 数据库与虚拟机匹配NUMA;
- 避免只绑CPU不绑内存;
- 了解自动NUMA balancing;
- 用真实负载验证,不盲目禁用。
十四、THP与HugeTLB
Transparent Huge Pages减少页表和TLB压力,但内存整理可能造成延迟。一些数据库建议调整THP模式。
cat /sys/kernel/mm/transparent_hugepage/enabled
grep -E 'AnonHugePages|HugePages' /proc/meminfo
显式HugeTLB需要预留,不可被普通内存随意使用。调整前评估应用、内核版本和启动参数。
十五、内存碎片与Compaction
总空闲内存足够仍可能无法分配高阶连续页。
cat /proc/buddyinfo
cat /proc/pagetypeinfo
grep -E 'compact|allocstall' /proc/vmstat
驱动、HugePage、网络和高阶分配可能受碎片影响。不能只靠重启解决,应分析工作负载和内核行为。
十六、脏页与写回
grep -E 'Dirty|Writeback' /proc/meminfo
sysctl vm.dirty_ratio vm.dirty_background_ratio
脏页过多时应用可能阻塞等待回写,看起来像内存问题,实际根因是存储写入能力不足。结合iostat和进程写入判断。
十七、缓存与tmpfs
tmpfs、/dev/shm和共享内存实际消耗内存,也可能计入容器限制。
df -hT
ipcs -m
findmnt -t tmpfs
浏览器、数据库、消息队列和机器学习程序可能大量使用共享内存。容器默认 /dev/shm 太小也会导致异常。
十八、语言运行时排障
Java
区分Heap、Metaspace、Direct、线程栈和Native;使用GC日志、JFR、jcmd和Heap Dump。生成Dump会消耗磁盘和暂停时间。
Go
查看Heap、Stack、MSpan/MCache和GC目标;使用pprof;注意RSS不会立即归还操作系统。
Python
关注对象引用、C扩展、allocator和多进程复制;使用tracemalloc等工具,但它不覆盖所有Native内存。
十九、故障处置路线
- 保存Meminfo、vmstat、PSI、进程PSS和Cgroup事件;
- 判断系统OOM还是Cgroup OOM;
- 区分匿名页、文件页、Slab、共享内存和HugePage;
- 停止非关键负载或限流;
- 保护核心服务;
- 必要时扩容;
- 对进程做Heap/Native分析;
- 复现并验证修复。
不要在没有证据时先清缓存或重启全部服务。
二十、监控与上线清单
- [ ] MemAvailable、Swap、PSI和回收速率受监控;
- [ ] 进程PSS、Cgroup current/max/events可查询;
- [ ] Slab、PageTable、共享内存和HugePage有基线;
- [ ] OOM日志集中并关联业务;
- [ ] 容器请求/限制基于真实峰值;
- [ ] JVM等运行时包含Native内存预算;
- [ ] NUMA、CPU和设备拓扑匹配;
- [ ] THP/Swap等参数经过压测而非模板复制;
- [ ] 磁盘回写与内存压力联合监控;
- [ ] 内存泄漏、节点压力和OOM场景已演练。
二十一、结语
Linux 内存排障要回答“内存被谁、以什么形式、在哪个限制域中占用”。Page Cache高并不等于泄漏,宿主机空闲也不代表容器不会OOM。通过PSS、Cgroup、PSI、Slab和NUMA等多视角建立证据,才能避免用重启和清缓存掩盖真正问题。
参考资料
- Linux Memory Management:https://docs.kernel.org/mm/
- PSI Documentation:https://docs.kernel.org/accounting/psi.html
- Control Group v2:https://docs.kernel.org/admin-guide/cgroup-v2.html