Linux 内存管理:交换、OOM 杀手和 Cgroups

12 分钟阅读 - 2026年5月31日

hero section cover
目录
  • Linux 内存管理详解:交换分区、OOM 杀手与 cgroups
  • Linux 如何管理内存页
  • 配置交换分区
  • OOM 杀手
  • Cgroups 和内存限制
  • 按服务器角色设置内存配置
分享

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

一个常见的错误:关注 freefree -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.swappinessvm.vfs_cache_pressure
通用 Web 服务器10–2050
数据库(MySQL/PostgreSQL)1–550
默认(大多数发行版)60100

关于交换空间的配置:对于处理偶发流量峰值的 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_memory0默认启用超额分配模式
vm.overcommit_memory2严格模式;防止分配量超过 RAM × overcommit_ratio + swap
vm.panic_on_oom1重启系统而非终止进程
vm.oom_kill_allocating_task1终止触发 OOM 的进程,而非最大的内存消耗者

若需主动监控,请查看 /proc/pressure/memory (压力停滞信息,自内核 4.20 起可用)。关注 some avg10 该值:低于 5% 表示系统健康,持续高于 20% 则意味着 OOM 事件可能即将发生。计数器 allocstall 计数器 /proc/vmstat 是另一个早期信号——它统计直接回收阻塞,这通常是 OOM 终止前的征兆。诸如 systemd-oomdearlyoom 等工具可在内核的 OOM 杀手触发前,根据 PSI 阈值采取行动。

Cgroups 和内存限制

控制组(cgroups)允许您将进程组织成组,并强制执行硬性资源限制。 该功能自 Linux 2.6.24 版本引入,是 DockerPodmanKubernetesLXC 等容器运行时的基础。内核会跟踪每个 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 v1Cgroup v2
层级多个,按控制器划分单一、统一
硬性内存限制memory.limit_in_bytesmemory.max
软内存限制memory.soft_limit_in_bytesmemory.high (限流)
使用情况追踪memory.usage_in_bytesmemory.current
压力指标受限PSI 集成

cgroup v2 中的关键内存控制:

参数类型描述
memory.max硬限制超过此值将触发 OOM 杀手
memory.high软限制在达到硬限制之前限制分配并触发回收
memory.low软保护低于此阈值的内存将最后被回收
memory.min硬保护低于此级别的内存绝不会被回收
memory.swap.max交换限制设置为 0 以禁用此 cgroup 的交换
memory.oom.group布尔值如果启用,OOM 会同时终止 cgroup 中的所有进程

一条实用规则:将 memory.highmemory.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.swappinessOOM策略Cgroup策略
数据库1–5保护 (OOMScoreAdjust=-900)使用 memory.min 来保护缓冲池
Web/应用服务器10–20默认通过 memory.max
后台工作进程60可终止 (OOMScoreAdjust=+200)通过以下方式限流 memory.high
多租户VPS60(使用 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.swappiness10–201–5
vm.vfs_cache_pressure5050
vm.dirty_ratio15%10%
vm.min_free_kbytes6553665536
内存不足保护默认OOMScoreAdjust=-1000

如果您正在运行高密度工作负载,且需要一台具备充足余量以正确应用这些策略的服务器,FDC的专用服务器值得您关注。

博客

本周特色

更多文章
Linux 内存管理:交换、OOM 杀手和 Cgroups

Linux 内存管理:交换、OOM 杀手和 Cgroups

Linux swap、OOM 杀手和 cgroups 如何协同工作--附带数据库、Web 服务器和多租户 VPS 主机的配置示例。

12 分钟阅读 - 2026年5月31日

Prometheus 和 node_exporter 安装指南

15 分钟阅读 - 2026年5月29日

更多文章