一、为什么要重新学会“看内存”
在 Ubuntu 上,新手习惯于打开系统监视器,扫一眼“用了多少 GB”就关闭;但当应用出现 OOM、数据库性能跳水、容器不断重启时,才发现“总占用”远不能说明问题:
-
可用内存为何突然缩水到几百 MB?
-
缓存与缓冲区算不算“已用”?
-
进程地图里“驻留”“共享”“匿名”各代表什么?
-
内核的内存回收水位线何时触发?
只有把“总览→进程→内核→硬件→调优”五个视角串成一条线,才能快速定位是“泄漏”“争用”还是“配置不合理”。下文用 3000 字拆解 Ubuntu 下查看内存使用情况的完整工具链与思路,全程不提任何云厂商,也不贴屏幕截图,让你即使面对无图形的服务器,也能靠命令行摸清每一兆内存的去向。
二、总览视角:一眼看清“物理-交换-缓存”三元组
-
物理内存
主板插槽提供的 DRAM 容量,是“所有分配”的源头。BIOS/UEFI 会预留一小部分,内核启动时再扣掉“内核代码+静态数据结构”,剩余部分纳入“伙伴系统”管理。
常见误区:把“总物理”当“可分配”。实际上内核预留、CMA(连续内存分配器)、透明巨型页都会吃掉一块,早期看不到,直到申请大页失败才暴露。 -
交换空间
swap 文件或 swap 分区并非“备用内存”,而是“内核回收的延伸”。当空闲页低于水位线,kswapd 会把匿名页换出到磁盘,腾出的 DRAM 供更活跃进程使用。
观察要点:swap 用量高 ≠ 内存不足。若回收速率与换入速率平衡,系统仍可保持流畅;但若出现大量换入换出,说明 DRAM 压力已超出水线。 -
缓存与缓冲区
读写文件时,内核把磁盘块留在 DRAM 形成 page cache,再次访问可直接命中,减少 I/O。缓存可随时丢弃,因此算“可用”的一部分。
常见疑问:“缓存太多会不会挤掉应用?”——内核有水位线控制,应用请求时会立即丢弃缓存页,不必手动清缓存;除非性能压测需要干净环境,否则清缓存只会让系统更慢。
三、进程视角:把“驻留-共享-匿名”拆开看
-
驻留集(RSS)
进程实际占据的物理页数,包含共享库、匿名映射、栈、堆。top/htop 默认按 RSS 排序,看似直观,却会把“共享库”重复计算——三个进程都加载 libc.so.6,RSS 会把同一段物理页算三遍。 -
比例共享集(PSS)
把共享库按引用次数均摊,更能反映“如果我杀掉这个进程,能释放出多少内存”。/proc/*/smaps 提供 PSS,可用 grep 累加,得到“去重”后的内存占用。 -
匿名映射
堆、栈、malloc/mmap 私有写时复制页,无法被磁盘文件回写,必须进 swap 才能回收。监控匿名页增长趋势,可提前发现内存泄漏。 -
Shared_Clean vs Shared_Dirty
共享且干净的页可直接丢弃;共享脏页需先写回,再丢弃。数据库场景常出现“共享脏页”暴涨,意味着刷盘线程跟不上写入速率。
四、内核视角:水位线、kswapd、OOM Killer
-
水位线(watermark)
每个 NUMA 节点都有三条水位:high、low、min。空闲页低于 low 时,内核唤醒 kswapd;低于 min 时,申请内存的进程会被阻塞,直到回收够页数。水位值在启动时根据“总内存百分比+固定偏移”计算,大内存机器偏移固定,导致“512 GB 机器仍因为低于 min 而阻塞”的怪事。 -
kswapd 行为
后台回收线程使用 LRU 算法,把“最不活跃”的页放到候选链表;匿名页被换出,文件页被丢弃或写回。kswapd 回收速度跟不上申请速度时,系统进入“直接回收”路径,任何 malloc 都会亲自扫 LRU,延迟飙升。 -
OOM Killer
当空闲+文件缓存总和低于 min,且回收无效,内核触发 OOM,挑选“OOM 得分最高”的进程杀掉。得分 = 驻留页数 + 内核补偿值 + 用户可调 oom_score_adj。数据库主机常把 oom_score_adj 调低,但仍被杀,原因是匿名页太大,得分居高不下。降低被杀概率的正确姿势是“减少匿名页+增加可用内存”,而非“一味调低得分”。
五、工具链:从“肉眼可见”到“原子级透视”
-
free
快速查看“物理-交换-缓存”三元组;关注 available 字段(内核估算的“无需 swap 就能立即分配”内存),比单纯的 free 更准确。 -
top / htop
动态排序 RSS;htop 可颜色区分常驻/共享/缓冲,F2 里打开“Also count shared”就能看到 PSS 近似值。 -
smaps / proc
cat /proc/PID/smaps 逐行给出 VMA 的 RSS/PSS/Shared/Private;累加即可得到“去重”后的进程内存。想定位泄漏,可间隔 10 秒采样,对比匿名页增长地址区间。 -
vmstat
1 秒刷新一次,r 列代表运行队列,si/so 代表换入换出速率,b 列代表因内存不足而阻塞的进程数;连续 si+so>0 且 b>0 说明内存已紧。 -
slabtop
内核 slab 分配器占用;小对象(task_struct、dentry、inode)暴涨时,slab 能占到几 GB,容易被误归到“缓存”里。 -
numastat
显示每个 NUMA 节点的内存平衡;node0 充足而 node1 低于 min 时,同样会触发 kswapd,多核机器需关注“远端内存”命中率。 -
dstat / sar
历史采样工具,能输出“内存-磁盘-网络-CPU”交叉曲线,适合复盘“昨晚 2 点为何 OOM”的场景。
六、图形与桌面:肉眼可见的“内存温度计”
-
GNOME System Monitor
资源标签页实时绘制“已用-缓存-可用”三色图;进程标签页可排序 RSS,但默认不看 PSS,需要插件补全。 -
KDE KSysGuard
支持自定义传感器公式,可把“PSS 总和 / 节点内存”做成百分比曲线,直观看到“去重”后的系统占用。 -
Conky
轻量级桌面嵌入工具,可把 free、vmstat、slabinfo 输出拼装成一行行彩色文字,适合开发机“边写代码边看内存”。 -
Mission Center / Netdata(本地版)
提供 WebUI,能下钻到“每个 cgroup 的内存使用率”,容器环境可一眼看出哪个容器接近限额。
七、容器与 cgroup:再看“内存限额”就懂了
-
memory.limit_in_bytes
cgroup v1 的硬限额;达到限额后,组内进程进入直接回收,延迟飙升,但不会被 OOM Kill,除非同时设置 memory.oom_control。 -
memory.high
cgroup v2 的“软限额”;超过后内核开始回收,但不会阻塞进程,适合“提前预警”而非“直接杀死”。 -
memory.current
实时统计当前 cgroup 的 RSS + 缓存(含 page cache);容器工具显示的“已用内存”即此值,因此“cat 大文件”也会让容器内存瞬间上涨,并非泄漏。 -
memory.stat
详细分解“rss、cache、inactive_anon、active_file”等;对比 inactive 与 active 比例,可判断“缓存是否可被快速回收”。
八、性能调优:让“看”变成“改”
-
降低水位线偏移
大内存机器可在 grub 里加vm.watermark_scale_factor=10,让 low/min 相对总内存的比例下降,减少“过早回收”。 -
调节 swappiness
数据库主机设vm.swappiness=1,让内核尽量回收文件缓存,少换匿名页,避免“换出热数据”导致的查询抖动。 -
HugeTLB
对于“几 GB 大页”需求,可预留vm.nr_hugepages=512,MySQL、PostgreSQL 挂载大页后,TLB 缺失率下降,CPU 利用率下降 5–10%。 -
slab_limit
vm.slab_reclaimable=0可禁止回收 dentry/inode 缓存,适合高 IOPS 文件服务器;但内存紧张时,可能触发更早 OOM,需要权衡。
九、故障案例复盘:从“现象”到“根因”的漏斗
-
案例:凌晨 3 点数据库被杀
现象:OOM Killer 杀掉 PostgreSQL;free 显示还有 8 GB 缓存。
根因:slab unreclaimable 涨到 12 GB,匿名页 50 GB,总和逼近 64 GB,可用< min,触发 OOM。
解决:关闭vm.slub_cpu_partial=0,并降低vm.vfs_cache_pressure,让 slab 可回收,后续监控 slab 占用。 -
案例:大文件导入后系统卡死
现象:导入 100 GB CSV,进度条到 70% 时界面无响应。
根因:page cache 涨到 45 GB,低于 min 水位,进入直接回收,IO 线程阻塞。
解决:导入前echo 3 > drop_caches清缓存,或调大 watermark_scale_factor,让系统更早后台回收。 -
案例:容器内存占用瞬涨 5 GB
现象:docker stats 显示容器用了 6 GB,但进程 RSS 只有 2 GB。
根因:容器内cat huge.log > /dev/null产生大量 page cache,算在 cgroup memory.current 里。
解决:在容器启动参数里加--memory-reservation让内核提前回收,或把日志目录挂载成 tmpfs,避免计入当前组。
十、安全与审计:让“看内存”成为例行公事
-
基线建立
每周采样“总内存-PSS-slab-缓存”四项指标,存入本地时序库,生成“七日移动平均”曲线;偏离超过 15% 自动告警,提前发现“缓慢泄漏”。 -
审计日志
把sar -r 1 0输出通过 rsyslog 发送到中央日志,保留 180 天;OOM 事件单独提取,包含“被杀进程名、PID、RSS、oom_score_adj”,方便合规审计。 -
权限最小化
给予开发人员sudo sysctl vm.drop_caches权限时,加审计规则,记录时间戳与人员;避免“为了清缓存而清缓存”的野蛮操作。 -
可视化大屏
把“可用内存、PSS、swap 速率、slab”四曲线投到大屏,颜色区分正常-警告-危险三段阈值;值班人员一眼就能判断“要不要扩容”,而不是等电话打进来。