为什么 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 上读取一个文件时,内核会将文件内容缓存到内存中。下次再次读取同一文件时,直接从内存返回------速度提升几个数量级。
bash
# 查看当前 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 缓冲等)的内存分配机制。
bash
# 查看 slab 使用情况
slabtop -o
# 查看 slab 在总内存中的占比
cat /proc/meminfo | grep -E "^Slab|^SReclaimable|^SUnreclaim"
其中:
- SReclaimable:可回收的 Slab 对象(如 dentry 缓存)
- SUnreclaim:不可回收的 Slab 对象
手动清理缓存(调试用)
bash
# 清理 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。直接查看它,可以获得更细粒度的信息:
bash
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
使用 top 或 ps 查看进程内存时,你会看到三个关键指标:
VIRT(Virtual Memory Size)------ 虚拟内存
进程申请的虚拟地址空间大小。包括:
- 进程的代码段、数据段、堆(heap)、栈(stack)
- 映射的文件(如共享库)
- 已申请但尚未实际分配的页面
bash
# 示例:一个 Python 进程
VIRT = 2.5G # 看起来很大,但一部分可能并未实际分配物理内存
VIRT 大不等于内存泄漏。很多进程预分配了大量虚拟地址空间(如 JVM),但实际使用的物理内存可能只有一小部分。
RES(Resident Size)------ 常驻内存
实际驻留在物理内存中的页面大小 。这是衡量进程真实内存占用的最可靠指标。
bash
# 按 RES 排序查看进程
top -o %MEM
# 或者
ps aux --sort=-%mem | head -10
RES 包括:
- 进程独占的匿名页面(堆、栈)
- 共享库中实际加载到内存的部分
- 映射文件中被缓存到内存的部分
注意:RES 包含共享内存中归属于该进程的部分,所以多个进程的 RES 之和可能超过总内存。
SHR(Shared Memory)------ 共享内存
进程实际使用的共享内存大小。典型场景:
- 共享库(libc.so、libpthread.so 等)的代码段
- IPC 共享内存
- tmpfs 中的文件
bash
# 查看共享内存使用情况
ipcs -m
实用技巧 :
top中按f键然后选择nTH可以显示进程的线程数 ;按f选择MEM可以看到内存占比。
核心监控工具实战
vmstat ------ 虚拟内存统计
bash
# 每 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% |
黄金指标 :如果 si 和 so 长期大于 0,说明系统真正内存不足,正在频繁换页。
sar -r ------ 历史内存趋势
bash
# 查看今天的每小时内存使用趋势
sar -r
# 每 2 秒收集一次,共 5 次
sar -r 2 5
# 查看特定日期的历史数据
sar -r -f /var/log/sysstat/sa23
sar -r 输出中的关键指标:
kbmemfree/kbmemused:空闲/已用内存kbbuffers/kbcached:缓冲区/缓存kbcommit/%commit:已提交的内存比例
top 和 htop
bash
# top 交互模式中的快捷键
top
# 按 M:按内存使用排序
# 按 P:按 CPU 使用排序
# 按 e:切换内存显示单位
# 按 f:选择要显示的列
htop 提供了更友好的界面,支持颜色编码和鼠标操作。
ps 快速排查
bash
# 按内存使用排序,查看 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
bash
# 查看当前 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 日志
bash
# 查看 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 来评分------分数越高的进程越容易被杀死。
bash
# 查看进程的 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?
- 配置合理的 swappiness:非数据库服务器可以保留默认值
- 设置进程内存限制(cgroups/systemd):
bash
# 通过 systemd 限制服务内存
systemctl set-property <service-name> MemoryMax=1G
- 关闭内存超额分配(谨慎!):
bash
# 0 = 启发式 overcommit(默认)
# 1 = 总是 overcommit
# 2 = 禁止 overcommit(拒绝超过 CommitLimit 的分配)
sysctl vm.overcommit_memory=2
sysctl vm.overcommit_ratio=50 # 允许 overcommit 的比例
- 使用限制内存的容器运行应用(如 Docker memory limits)
- 监控
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 |
ps aux --sort=-%mem |
进程内存排序 | `ps aux --sort=-%mem |
sar -r |
历史内存趋势 | sar -r -f /var/log/sysstat/sa23 |
slabtop -s c |
查看 slab 缓存 | `slabtop -o |
| `dmesg | grep -i oom` | OOM 日志 |
ipcs -m |
共享内存段 | ipcs -m -u |
sysctl vm.swappiness |
查看/设置 swappiness | sysctl -w vm.swappiness=10 |
希望这篇文章能帮助你更好地理解 Linux 内存管理。如果你有任何问题或发现需要补充的内容,欢迎交流讨论!