为什么 Linux 内存管理值得你花时间理解?

如果你曾经在 Linux 服务器上运行过 free -h,看到这样的输出:

              total        used        free      shared  buff/cache   available
Mem:           7.6G        5.2G        200M        120M        2.2G        6.8G
Swap:          4.0Gi          0B       4.0Gi

第一反应可能是——“天哪,用了 5.2G,只剩 200M 空闲了?是不是该加内存了?”

停。

这正是本文想要解决的第一个(也是最大的)误解。在 Linux 世界中,“已用"不意味着"已满”,“空闲"也不代表"可用”。5.2G 的 used 中,有 2.2G 是 buff/cache——而这些 cache,在应用程序真正需要时,是可以被内核回收的。

理解 Linux 内存管理,不仅能帮你避免不必要的硬件采购,还能在真正出现内存泄漏或 OOM(Out-Of-Memory)问题时,快速定位根因


free 命令全面解读

基础输出结构

运行 free -k(以 KB 为单位输出):

              total        used        free      shared  buff/cache   available
Mem:        8009416     5463124      204892      123456     2341400     7102344
Swap:       2097148           0     2097148

每一列的含义:

含义 说明
total 总物理内存 从硬件检测到的 RAM 总量
used 已使用内存 total - free - buff/cache,注意这并不是"不可回收"的内存
free 完全未使用的内存 没有被任何进程或内核使用的"裸空闲"内存
shared 共享内存 主要用于 tmpfs、共享内存段(如 IPC)、Docker 容器等
buff/cache 缓冲区 + 缓存 内核用这部分内存来加速磁盘 I/O 和文件系统访问
available 可用内存 最关键的一列——新应用程序在不触发 swap 的情况下可以使用的内存量

核心要点:为什么 available 比 free 重要

free(空闲内存)告诉你的是完全没有任何用途的内存数量。但 Linux 的设计哲学是:空闲内存就是浪费的内存

Linux 内核会积极地将空闲内存用于:

  • Page Cache(页面缓存):缓存磁盘文件内容,加速文件读写
  • Buffer Cache(缓冲区缓存):缓存块设备元数据
  • Slab 缓存:缓存内核对象(如 inode、dentry)

这些 cache 在应用程序需要更多内存时,可以被立即回收。这就是为什么 available 列被引入的初衷——它估算的是实际可用的内存量。

在较新的内核(3.14+)中,available 的计算考虑了:

  • MemFree
  • 可回收的 Page Cache
  • SReclaimable(可回收的 Slab 对象)
  • 文件备份页面的比例

buff/cache 深度解析

Page Cache(页面缓存)

当你在 Linux 上读取一个文件时,内核会将文件内容缓存到内存中。下次再次读取同一文件时,直接从内存返回——速度提升几个数量级

# 查看当前 page cache 大小
cat /proc/meminfo | grep -E "^Cached|^SwapCached"

Page Cache 是 Linux 内存使用中占比最大的部分。一个长期运行的服务器,大量内存被 Page Cache 占据是完全正常的。

Buffer Cache(缓冲区缓存)

Buffer Cache 缓存的是块设备的元数据,比如文件系统的超级块、inode 表等。在较新的内核中,Buffer Cache 和 Page Cache 已经部分合并,但在 /proc/meminfo 中仍然分开显示。

Slab 缓存

Slab 是内核用来管理小对象(如 inode、dentry、socket 缓冲等)的内存分配机制。

# 查看 slab 使用情况
slabtop -o

# 查看 slab 在总内存中的占比
cat /proc/meminfo | grep -E "^Slab|^SReclaimable|^SUnreclaim"

其中:

  • SReclaimable:可回收的 Slab 对象(如 dentry 缓存)
  • SUnreclaim:不可回收的 Slab 对象

手动清理缓存(调试用)

# 清理 page cache(仅 pagecache)
sync && echo 1 > /proc/sys/vm/drop_caches

# 清理 dentries 和 inodes
sync && echo 2 > /proc/sys/vm/drop_caches

# 清理 pagecache、dentries 和 inodes
sync && echo 3 > /proc/sys/vm/drop_caches

⚠️ 警告drop_caches 仅供测试和调试使用。在生产环境中,不要依赖它来"释放内存"。内核会自动管理缓存回收,手动清理反而会因为丢失缓存而导致暂时性的性能下降。


/proc/meminfo:内存信息的终极来源

free 命令的所有数据都来自 /proc/meminfo。直接查看它,可以获得更细粒度的信息:

cat /proc/meminfo

关键字段及其关系:

MemTotal:        8009416 kB    # 总物理内存
MemFree:          204892 kB    # 完全空闲的内存(等同于 free 命令的 free)
MemAvailable:    7102344 kB    # 实际可用内存(关键指标)

Buffers:          307200 kB    # 块设备缓冲区
Cached:          1943200 kB    # Page Cache + tmpfs
SwapCached:            0 kB    # 已换出但仍在 swap 中的页

Active:          4200000 kB    # 最近被访问过的内存页
Inactive:        2800000 kB    # 较久未被访问的内存页
Active(anon):    2800000 kB    # 活跃的匿名页(进程堆栈等)
Inactive(anon):  1200000 kB    # 非活跃匿名页
Active(file):    1400000 kB    # 活跃的文件备份页
Inactive(file):  1600000 kB    # 非活跃文件备份页

Unevictable:       20000 kB    # 不可被换出的页面(如 mlock)
Mlocked:           20000 kB    # 通过 mlock() 锁定的页面

SwapTotal:       2097148 kB    # 交换分区总大小
SwapFree:        2097148 kB    # 交换分区可用大小

Dirty:              1200 kB    # 等待写入磁盘的脏页
Writeback:             0 kB    # 正在写回磁盘的页面

Slab:             450000 kB    # 内核 Slab 对象总大小
SReclaimable:     350000 kB    # 可回收的 Slab(如 dentry 缓存)
SUnreclaim:       100000 kB    # 不可回收的 Slab

PageTables:        32000 kB    # 页表占用的内存(进程越多越大)
NFS_Unstable:          0 kB    # NFS 未确认的页面
Bounce:                0 kB    # 用于 bounce buffer 的内存

CommitLimit:     6100000 kB    # 基于 overcommit 策略的提交限制
Committed_AS:    5500000 kB    # 已commit的虚拟内存总量

MemAvailable 是"估算值"

MemAvailable 不是直接测量的值,而是内核基于当前状态估算出来的。它的计算方式大致是:

MemAvailable ≈ MemFree + (PageCache 可回收部分) + (SReclaimable 可回收部分) - 低水位预留

这意味着即使在内存压力下,这个值也会动态变化。它是监控中最可靠的单一指标。

Active 与 Inactive 的意义

内存页分为活跃(Active)和非活跃(Inactive)两种状态:

  • Active:最近被访问过的页面,内核尽量保留
  • Inactive:已经有一段时间未被访问,是内核回收的首选

当系统可用内存减少时,内核会首先扫描并回收 Inactive 列表中的页面。


进程级别的内存:VIRT、RES、SHR

使用 topps 查看进程内存时,你会看到三个关键指标:

VIRT(Virtual Memory Size)—— 虚拟内存

进程申请的虚拟地址空间大小。包括:

  • 进程的代码段、数据段、堆(heap)、栈(stack)
  • 映射的文件(如共享库)
  • 已申请但尚未实际分配的页面
# 示例:一个 Python 进程
VIRT = 2.5G    # 看起来很大,但一部分可能并未实际分配物理内存

VIRT 大不等于内存泄漏。很多进程预分配了大量虚拟地址空间(如 JVM),但实际使用的物理内存可能只有一小部分。

RES(Resident Size)—— 常驻内存

实际驻留在物理内存中的页面大小。这是衡量进程真实内存占用的最可靠指标

# 按 RES 排序查看进程
top -o %MEM

# 或者
ps aux --sort=-%mem | head -10

RES 包括:

  • 进程独占的匿名页面(堆、栈)
  • 共享库中实际加载到内存的部分
  • 映射文件中被缓存到内存的部分

注意:RES 包含共享内存中归属于该进程的部分,所以多个进程的 RES 之和可能超过总内存。

SHR(Shared Memory)—— 共享内存

进程实际使用的共享内存大小。典型场景:

  • 共享库(libc.so、libpthread.so 等)的代码段
  • IPC 共享内存
  • tmpfs 中的文件
# 查看共享内存使用情况
ipcs -m

实用技巧top 中按 f 键然后选择 nTH 可以显示进程的线程数;按 f 选择 MEM 可以看到内存占比。


核心监控工具实战

vmstat —— 虚拟内存统计

# 每 2 秒输出一次,显示单位为 K
vmstat 2

# 清晰的宽格式输出
vmstat -w 2

关键列解读:

含义 正常值
r 运行队列中的进程数 < CPU 核数 x 2
b 不可中断休眠进程数 接近 0
swpd 已使用的 swap 尽量接近 0
free 空闲内存 不重要,看 available
buff 缓冲区 随负载变化
cache 页面缓存 越大越好
si 从 swap 换入 长期 > 0 需要关注
so 换出到 swap 长期 > 0 需要关注
bi 块设备读入 随 I/O 负载变化
bo 块设备写出 随 I/O 负载变化
wa CPU 等待 I/O 时间 正常 < 10%

黄金指标:如果 siso 长期大于 0,说明系统真正内存不足,正在频繁换页。

sar -r —— 历史内存趋势

# 查看今天的每小时内存使用趋势
sar -r

# 每 2 秒收集一次,共 5 次
sar -r 2 5

# 查看特定日期的历史数据
sar -r -f /var/log/sysstat/sa23

sar -r 输出中的关键指标:

  • kbmemfree / kbmemused:空闲/已用内存
  • kbbuffers / kbcached:缓冲区/缓存
  • kbcommit / %commit:已提交的内存比例

tophtop

# top 交互模式中的快捷键
top
# 按 M:按内存使用排序
# 按 P:按 CPU 使用排序  
# 按 e:切换内存显示单位
# 按 f:选择要显示的列

htop 提供了更友好的界面,支持颜色编码和鼠标操作。

ps 快速排查

# 按内存使用排序,查看 Top 10 进程
ps aux --sort=-%mem | head -10

# 查看特定进程的内存
ps -p <PID> -o pid,ppid,rss,vsz,%mem,cmd

Swap 深度理解

什么是 Swap?

Swap 是磁盘上用于扩展内存的空间。当物理内存不足时,内核会将不活跃的内存页换出(swap out)到磁盘,腾出物理内存给活跃进程。

Swap 是好是坏?

这是 Linux 社区最常见的争论之一。正确的理解是:

Swap 不是"紧急逃生通道"——它是内存层次结构中的正常一层。

  • 好的 Swap 使用:将长时间不活跃的进程页面换出,让缓存有更多空间加速文件访问
  • 坏的 Swap 使用:因内存不足,导致活跃进程的页面也被频繁换入换出(thrashing)

关键参数:swappiness

# 查看当前 swappiness 值
cat /proc/sys/vm/swappiness

# 临时修改(重启后失效)
sysctl vm.swappiness=10

# 永久修改
echo "vm.swappiness=10" >> /etc/sysctl.conf

swappiness 范围 0~100(默认 60):

  • 值越低:越倾向于使用物理内存,只在内存极度紧张时才 swap
  • 值越高:越激进地使用 swap(但并不意味着性能更好)

对于现代服务器(特别是 SSD),推荐值:

  • 数据库服务器:1~10
  • Web 服务器:10~30
  • 桌面系统:60(默认)

Swap 与 OOM 的关系

很多人认为"加 swap 可以防止 OOM"。不完全正确

Swap 只是延缓了 OOM 的发生。如果内存泄漏的速度超过了 swap 空间 + 物理内存的总和,OOM Killer 仍然会被触发。

更准确的说法是:充足的 swap 可以给运维人员争取时间来诊断问题。


OOM Killer:最后的防线

何时触发 OOM Killer?

当系统物理内存 + swap 均已耗尽,且内核的"内存超额分配"(overcommit)策略导致无法满足新的内存申请时,OOM Killer 会选择一个进程杀死。

查看 OOM Killer 日志

# 查看 OOM Killer 活动记录
dmesg | grep -i "killed process"

# 或者查看内核日志
grep -i "oom" /var/log/messages
# 或者
grep -i "oom" /var/log/kern.log

典型 OOM 日志:

[123456.789] oom-killer: gfp_mask=0xd0, order=0, oom_score_adj=0
[123456.789] [ pid ]   uid  tgid total_vm      rss nr_ptes nr_pmds swapents oom_score_adj name
[123456.789] [ 1234]     0  1234  1234567    98765    123      4     5678             0 java
[123456.789] Out of memory: Killed process 1234 (java) total-vm:1234567kB, anon-rss:98765kB, file-rss:0kB

OOM Killer 选择谁?

内核使用 oom_score 来评分——分数越高的进程越容易被杀死。

# 查看进程的 oom_score
cat /proc/<PID>/oom_score

# 调整进程的 OOM 优先级(-1000 到 1000)
echo -500 > /proc/<PID>/oom_score_adj

影响 oom_score 的因素:
oom_score 的计算几乎纯粹取决于该进程占用的内存比例(实际物理内存 RSS + 换出的 Swap 空间),再叠加上 oom_score_adj 的修正值

如何防止 OOM?

  1. 配置合理的 swappiness:非数据库服务器可以保留默认值
  2. 设置进程内存限制(cgroups/systemd):
# 通过 systemd 限制服务内存
systemctl set-property <service-name> MemoryMax=1G
  1. 关闭内存超额分配(谨慎!):
# 0 = 启发式 overcommit(默认)
# 1 = 总是 overcommit
# 2 = 禁止 overcommit(拒绝超过 CommitLimit 的分配)
sysctl vm.overcommit_memory=2
sysctl vm.overcommit_ratio=50  # 允许 overcommit 的比例
  1. 使用限制内存的容器运行应用(如 Docker memory limits)
  2. 监控 available 内存,设置合理告警阈值

本文参考自 Heroix Blog - Linux Memory Use,这是一篇经典的 Linux 内存管理入门文章。其核心观点——“Because Linux uses free memory for buffers and caches, monitoring for memory problems is more complex than simply looking at the free memory value on the system”——至今仍然是每一位 Linux 运维人员需要铭记的原则。


附录:速查命令表

命令 用途 示例
free -h 查看内存概览 free -h
cat /proc/meminfo 查看详细内存指标 grep Mem /proc/meminfo
vmstat -w 2 实时虚拟内存统计 vmstat -w 2 10
top -o %MEM 按内存排序查看进程 top -b -n 1 -o %MEM | head -20
ps aux --sort=-%mem 进程内存排序 ps aux --sort=-%mem | head -10
sar -r 历史内存趋势 sar -r -f /var/log/sysstat/sa23
slabtop -s c 查看 slab 缓存 slabtop -o | head -10
dmesg | grep -i oom OOM 日志 dmesg | grep -i "killed process"
ipcs -m 共享内存段 ipcs -m -u
sysctl vm.swappiness 查看/设置 swappiness sysctl -w vm.swappiness=10

希望这篇文章能帮助你更好地理解 Linux 内存管理。如果你有任何问题或发现需要补充的内容,欢迎交流讨论!

Logo

免费领 150 小时云算力,进群参与显卡、AI PC 幸运抽奖

更多推荐