Linux 内存管理:交换、OOM 杀手和 Cgroups
12 分钟阅读 - 2026年5月31日

Linux swap、OOM 杀手和 cgroups 如何协同工作--附带数据库、Web 服务器和多租户 VPS 主机的配置示例。
Linux 内存管理详解:交换分区、OOM 杀手与 cgroups
Linux 处理内存的方式与大多数操作系统不同。内存使用率过高并不总是问题——内核会主动利用空闲内存进行缓存,以加快磁盘读取速度。但当实际内存压力增大时,有三种机制会发挥作用:交换分区、OOM 杀手和 cgroups。了解它们各自的行为方式以及如何配置,将决定服务器在负载下是能平稳降级,还是会毫无预兆地崩溃。
Linux 如何管理内存页
每个进程都在其自身的虚拟地址空间中运行,在 64 位系统上最大可达 128 TB。内核通过页表将这些虚拟地址映射到物理 RAM,并利用翻译旁路缓冲区 (TLB) 缓存最近的查找记录。TLB 命中耗时约 1 纳秒;未命中则需 20–100 纳秒,这在数据库等内存密集型工作负载中会累积成显著开销。
物理内存被划分为 4 KB 的页面,内核将其分为两类:
- 文件支持页——与磁盘上的文件相关联。内核可以丢弃干净的页面,或刷新脏页面,而无需使用交换空间。
- 匿名页——没有对应文件的堆和栈内存。内核必须先将这些页写入交换空间,才能释放它们。
在内存需求较高的服务器上,若匿名页占比过大,将导致交换区过早介入。请关注 si (swap in) 和 so (swap out) 列 vmstat 1 ——若这些值持续不为零,即为系统承受压力的首个预警信号。
监控时请选用合适的工具:
| 工具 | 最适合 | 关键指标 |
|---|---|---|
free -h | 快速全系统快照 | available 列 |
vmstat 1 | 实时交换空间和 I/O 监控 | si, so |
htop | 交互式按进程视图 | 内存条、进程列表 |
smem | 精确的按进程使用情况 | USS(唯一集大小) |
/proc/meminfo | 内核级详细信息 | MemAvailable, Dirty, Slab |
一个常见的错误:关注 free 列 free -h 列,并因此惊慌失措。真正重要的是 available 列才真正重要。它包含内核可按需从缓存中回收的内存。一台服务器显示仅剩 512 MB 空闲内存但可用内存为 5 GB 时,并不意味着系统已出问题。
当内存低于阈值时,内核的 kswapd 守护进程会开始在后台回收内存页。若回收量不足,内核将转入直接回收模式,阻塞进程直至内存页被释放。这就是延迟骤升的根源。建议在 MemAvailable 降至总内存的10–15%时设置警报,以便您有时间做出响应。
配置交换分区
交换分区(Swap)是一块磁盘区域(可以是分区或文件),当内存(RAM)已满时,内核会将不活跃的匿名页面移至此处。 两者在速度上存在显著差距:DDR4内存的延迟约为100纳秒,而NVMe固态硬盘约为100,000纳秒,SATA固态硬盘则接近500,000纳秒。交换分区是安全缓冲区,而非额外的内存。如果服务器长期依赖交换分区,说明其存在内存问题,增加交换分区并不能解决该问题。
建议使用交换文件而非交换分区。这样更容易调整大小,且无需重新分区。
sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile将文件添加到 /etc/fstab 才能在重启后保留数据。 chmod 600 这一步是必需的——任何从内存中换出的数据均可从交换区读取,因此该文件绝不能对所有人可读。
创建交换空间后,调整 vm.swappiness。默认值60过于激进。对于大多数托管工作负载,应让内核优先使用内存,仅在万不得已时才使用交换空间:
| 服务器角色 | vm.swappiness | vm.vfs_cache_pressure |
|---|---|---|
| 通用 Web 服务器 | 10–20 | 50 |
| 数据库(MySQL/PostgreSQL) | 1–5 | 50 |
| 默认(大多数发行版) | 60 | 100 |
关于交换空间的配置:对于处理偶发流量峰值的 2 GB VPS,1–2 GB 已足够。在 8 GB 或更大内存的系统上,通常 2–4 GB 的固定交换空间就足够了。其目的是为内核提供一个处理冷页的缓冲区,而非扩展总可寻址内存。
在内存受限但 CPU 资源充足的服务器上,zram 可在内存中创建压缩的交换区,从而完全避免磁盘 I/O。对于租户间共享 NVMe 的多租户 VPS 主机,值得考虑使用 zram。若交换分区与数据库文件位于同一设备上,需注意 I/O 争用问题——频繁的交换操作与高吞吐量的磁盘写入难以共存。
OOM 杀手
当内核耗尽 RAM 和交换空间,且无法通过其他方式回收足够内存时,OOM 杀手便会介入。它会使用 oom_badness() 函数对进程进行评分:
points = (rss_anon + rss_file + rss_shmem + swapents + pgtables_pages) + (oom_score_adj × totalpages / 1000)得分最高的进程将被终止。该公式倾向于优先处理占用大量内存的进程,并且内核会检查进程在过去5秒内是否已被终止,以此避免在短时间内连续终止多个进程。
日志中会出现两种类型的 OOM 事件:
- 全局 OOM — 整个系统已耗尽 RAM 和交换空间。日志前缀为
Out of memory: - Cgroup OOM — 某个容器或服务触及其
memory.max限制。日志前缀为Memory cgroup out of memory:
查看历史 OOM 事件:
dmesg -T | grep -i "out of memory"
journalctl -k --grep="oom"请关注 order 字段。该值大于 0 表明是内存碎片化而非完全耗尽——即使有可用内存,内核也无法找到足够的连续页面。
您可以通过调整 /proc/<pid>/oom_score_adj。取值范围为 -1000(永不终止)到 +1000(优先终止)。对于由 systemd 管理的服务,请在单元文件中永久设置此参数:
[Service]
OOMScoreAdjust=-1000用于调整 OOM 行为的其他 sysctl 参数:
| 参数 | 值 | 效果 |
|---|---|---|
vm.overcommit_memory | 0 | 默认启用超额分配模式 |
vm.overcommit_memory | 2 | 严格模式;防止分配量超过 RAM × overcommit_ratio + swap |
vm.panic_on_oom | 1 | 重启系统而非终止进程 |
vm.oom_kill_allocating_task | 1 | 终止触发 OOM 的进程,而非最大的内存消耗者 |
若需主动监控,请查看 /proc/pressure/memory (压力停滞信息,自内核 4.20 起可用)。关注 some avg10 该值:低于 5% 表示系统健康,持续高于 20% 则意味着 OOM 事件可能即将发生。计数器 allocstall 计数器 /proc/vmstat 是另一个早期信号——它统计直接回收阻塞,这通常是 OOM 终止前的征兆。诸如 systemd-oomd 或 earlyoom 等工具可在内核的 OOM 杀手触发前,根据 PSI 阈值采取行动。
Cgroups 和内存限制
控制组(cgroups)允许您将进程组织成组,并强制执行硬性资源限制。 该功能自 Linux 2.6.24 版本引入,是 Docker、Podman、Kubernetes 和 LXC 等容器运行时的基础。内核会跟踪每个 cgroup 的内存使用情况,涵盖匿名内存、文件支持的页面以及内核对象。如果某个 cgroup 达到限制,内核将回收该组内的内存,或触发 cgroup 范围内的 OOM 终止。
Cgroup v1 和 v2 的主要区别在于其结构。v1 将每个控制器(内存、CPU、I/O)分别挂载在 /sys/fs/cgroup/<controller>/下分别挂载,这导致资源追踪不一致。V2 则采用统一的层级结构 /sys/fs/cgroup/。Kubernetes 在 1.25 版本中将 v2 设为默认,并在 1.31 版本中停止了对 v1 的支持。
要检查系统使用的版本:
stat -fc %T /sys/fs/cgroup/cgroup2fs 表示 v2; tmpfs 通常表示 v1。
| 功能 | Cgroup v1 | Cgroup v2 |
|---|---|---|
| 层级 | 多个,按控制器划分 | 单一、统一 |
| 硬性内存限制 | memory.limit_in_bytes | memory.max |
| 软内存限制 | memory.soft_limit_in_bytes | memory.high (限流) |
| 使用情况追踪 | memory.usage_in_bytes | memory.current |
| 压力指标 | 受限 | PSI 集成 |
cgroup v2 中的关键内存控制:
| 参数 | 类型 | 描述 |
|---|---|---|
memory.max | 硬限制 | 超过此值将触发 OOM 杀手 |
memory.high | 软限制 | 在达到硬限制之前限制分配并触发回收 |
memory.low | 软保护 | 低于此阈值的内存将最后被回收 |
memory.min | 硬保护 | 低于此级别的内存绝不会被回收 |
memory.swap.max | 交换限制 | 设置为 0 以禁用此 cgroup 的交换 |
memory.oom.group | 布尔值 | 如果启用,OOM 会同时终止 cgroup 中的所有进程 |
一条实用规则:将 memory.high 比 memory.max ,以便内核在触及硬限制前有空间回收。在确定 memory.max时,请在应用程序峰值使用量基础上增加 20–30%,以考虑页面缓存——该缓存会占用 cgroup 内存总量。
通过 systemd 管理 cgroup,而非直接写入 cgroup 文件系统。使用 unit 文件指令,例如 MemoryMax=, MemoryHigh=和 MemoryMin= 来设置持久化限制。快速测试时:
systemd-run --scope -p MemoryMax=512M <command>对于 Web 服务器工作池,设置 memory.oom.group=1 可确保当某个工作进程超出限制时能被干净地终止——不会留下任何孤立进程。对于数据库引擎, memory.min 可防止缓冲池在系统级压力下被回收。
按服务器角色设置内存配置
正确的内存设置取决于服务器的具体用途。若将同一配置同时应用于数据库服务器和 PHP Web 服务器,其中一方的性能将受到影响。
| 服务器角色 | vm.swappiness | OOM策略 | Cgroup策略 |
|---|---|---|---|
| 数据库 | 1–5 | 保护 (OOMScoreAdjust=-900) | 使用 memory.min 来保护缓冲池 |
| Web/应用服务器 | 10–20 | 默认 | 通过 memory.max |
| 后台工作进程 | 60 | 可终止 (OOMScoreAdjust=+200) | 通过以下方式限流 memory.high |
| 多租户VPS | 60(使用 zram) | 默认 | 通过 memory.max |
对于 MySQL 和 PostgreSQL,请为 innodb_buffer_pool_size,禁用透明巨页以减少延迟峰值,并通过 OOMScoreAdjust=-900 在 systemd 单元文件中保护该进程。
对于 PHP-FPM,应根据实际内存使用情况调整工作进程池的大小。每个工作进程通常占用 30–100 MB。将分配的 RAM 除以平均工作进程大小,即可得出一个安全的 pm.max_children 值。使用 memory.max cgroups 中的来限制工作池大小。
对于写入密集型工作负载,将 vm.dirty_ratio 为约 10%,并将 vm.dirty_background_ratio 3%。这会更频繁地刷新脏页,从而避免大规模的 I/O 阻塞。
将参数保存至 /etc/sysctl.d/90-memory.conf。运行时应用的设置在重启后会丢失。
按角色划分的推荐值汇总:
| 参数 | Web/应用服务器 | 数据库服务器 |
|---|---|---|
vm.swappiness | 10–20 | 1–5 |
vm.vfs_cache_pressure | 50 | 50 |
vm.dirty_ratio | 15% | 10% |
vm.min_free_kbytes | 65536 | 65536 |
| 内存不足保护 | 默认 | OOMScoreAdjust=-1000 |
如果您正在运行高密度工作负载,且需要一台具备充足余量以正确应用这些策略的服务器,FDC的专用服务器值得您关注。

Linux 内存管理:交换、OOM 杀手和 Cgroups
Linux swap、OOM 杀手和 cgroups 如何协同工作--附带数据库、Web 服务器和多租户 VPS 主机的配置示例。
12 分钟阅读 - 2026年5月31日
Prometheus 和 node_exporter 安装指南
15 分钟阅读 - 2026年5月29日