🐧 第03章 · 内存与交换

free top vmstat swap

内存与交换

内存不够用了怎么办?杀进程还是加交换?这一章讲清内存的分配、缓存与回收机制,以及 free、top、vmstat、swap 的用法。先理解概念,再动手,避免"看 stats 蒙圈、乱杀进程"。

内存的组成

Linux 内存不是"用了多少就是多少",free -h 的输出要这样读:

free -h
               total   used   free  shared  buff/cache  available
Mem:            15Gi  7.0Gi  269Mi  285Mi     9.0Gi      8.6Gi
Swap:            0B     0B     0B
  • total —— 物理内存总量(本机 15Gi);
  • used —— 真正被应用占用的;
  • free —— 完全空闲未分配的;
  • buff/cache —— 内核把磁盘数据缓冲/缓存在内存,用于加速读文件,可回收;
  • available —— 真正可用的内存,看它比看 free 靠谱得多(它把可回收的 cache 也算进去了)。

易错 看到 free 只剩几百 M 就慌,其实是 buff/cache 占了 9G,而 available 还有 8.6G。Linux 的哲学是"闲置内存不如用来做 cache",所以 free 显示小、available 显示大才是常态。判断内存压力看 available,不看 free。

进程占用内存:top 与 ps

top 是实时交互工具,按下 P 按 CPU 排、M 按内存排,q 退出:

top                      # 交互模式
top -bn1 | head -20      # -b 批处理,-n1 只输出一次,适合脚本抓快照
ps aux --sort=-%mem       # 按内存占用降序(负号倒序)
ps aux --sort=-%mem | head   # 只看内存最多的前几行

易错 top 里 %MEM 的单进程内存是"常驻集 (RSS)",不含共享库;带一堆子进程的 Node 应用要按整个进程树累加,别只看一行。ps aux 的 %MEM 是最朴素的内存占比,配合 -o 可只看想看的列。

ps -eo pid,comm,%mem,rss --sort=-%mem | head   # 自定义列查看

CPU 也在消耗:cache 与内存回收

很多"内存告急"其实是可回收的 cache,内核有回收机制按需释放。/proc/sys/vm 下的参数控制回收策略:

sysctl vm.swappiness          # 0~100,越大越倾向把内存换到 swap
sysctl vm.overcommit_memory   # 内存超卖策略
cat /proc/sys/vm/cache_recently_evicted   # 观察 cache 回收

vm.swappiness 默认 60,本机 没有配置 swap(Swap: 0B),因此该值对本地回收无意义。若要临时释放缓存(不建议在生产随意执行):

echo 1 > /proc/sys/vm/drop_caches    # 清页缓存,需 root(生产谨慎,会短暂变慢)

易错 drop_caches 会导致缓存重建,写入大文件的应用会瞬间变慢。它不是"省内存"的常规手段,只在调试时才用。生产上优先检查是不是应用泄漏。

内存动态:vmstat

vmstat 看内存的实时变化趋势,比单次快照更能说明问题:

vmstat 1 5      # 每 1 秒采样 1 次,共 5 次
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 1  0      0  269M   ...    ...     0    0     0     0   ...  ...  2  1 97  0  0

关键列:si/so(swap 换入换出,越大内存越吃紧)、free(空闲)、cache、wa(等待磁盘 I/O)。r(运行队列)与 b(阻塞队列)反映 CPU/IO 是否拥挤。

提示 si/so 持续非零是内存不足的强信号。本机无 swap,si/so 恒为 0;若某天内存打满,.service 会直接 OOM 被杀——这也是不做 swap 的代价。

OOM:内存不足时内核怎么处理

内存耗尽时,Linux 会选择一个进程杀掉(Out Of Memory killer)。谁被杀了能查:

dmesg -T | grep -i "out of memory"        # 查看 OOM 记录
journalctl -k | grep -i "oom"             # 内核日志里的 OOM(更完整)

被 OOM 杀掉的常见是占用内存最大的进程,也可能因 oom_score 权重不同而变。看当前各个进程的 OOM 评分:

for p in /proc/[0-9]*/oom_score; do
  pid=$(basename $(dirname $p))
  echo "$pid $(cat $p) $(cat /proc/$pid/comm)"; done | sort -rn | head

易错 看到"我的进程被杀了"不要只怪内核——根因通常是内存真的不够,或某个进程泄漏。加 swap 能减少 OOM 但不能根治泄漏,先找到谁在吃内存,ps aux --sort=-%mem 排一排。

配置交换(swap)——本机示例

本机 Swap: 0B,如果用 Node/MySQL 的内存需求大,加一个 swap 文件是稳妥的兜底(dram 充足时不强制,但建议有):

fallocate -l 2G /swapfile           # 创建 2G 交换文件(本机可改成 4G)
chmod 600 /swapfile                 # 权限收紧,安全要求
mkswap /swapfile                    # 初始化为 swap
swapon /swapfile                    # 启用
# 写入 fstab 使其开机自动挂载
echo "/swapfile none swap sw 0 0" >> /etc/fstab
swapon --show                       # 确认生效

易错 swap 文件权限一定要 600,否则 mkswap 会警告"insecure permissions"。swapon 后建议重启或 systemctl restart ... 验证。swap 是"缓刑"不是"解决",内存泄漏的应用照样会在 swap 耗尽后被 OOM。

案例:Node 应用占内存过高

本机 Node 应用(/var/www/bangdream、端口 3000)如果某天内存涨到临界,先按顺序排查:

ps aux --sort=-%mem | head      # 谁吃了内存
pm2/systemctl status bangdream   # 看服务状态
journalctl -u bangdream --since "1 hour ago" | tail   # 看是否报错或泄漏
# 确认是泄漏后再重启:systemctl restart bangdream

重启只是临时止血;反复出现说明应用代码有内存泄漏,需要去 Node 侧定位(heap 快照、--trace-gc 等)。

易错点

  • 只盯 free 不盯 available:内存压力的正确指标是 available。
  • 内存被 buff/cache 占了就以为不够:cache 可回收,不是"白占"。
  • 无 swap 的机器内存打满会被 OOM 强杀:重要服务建议配置 swap 兜底。
  • 拿到内存异常就 drop_caches:会变慢,先定位泄漏进程。

相关命令

free、top、vmstat、ps atop、pmap、fallocate、mkswap、swapon、sysctl、dmesg。