分类
devops

深入理解 Linux 虚拟内存:从 Swap 逻辑到 OOM 生存指南

Virtual Machine 虚拟机器
Virtual Memory 虚拟存储器

我们经常会听到“内存满了”、“触发 OOM”或者“Swap 抖动”等词汇。但在深陷排障之前,理清虚拟内存(Virtual Memory)的底层逻辑至关重要。


虚拟内存 vs 物理内存:不是“二选一”

首先要纠正一个常见的误区:关闭 Swap 并不代表关闭了虚拟内存。

  • 物理内存 (RAM):你插在主板上的硬件。
  • 虚拟内存 (VM):这是 Linux 内核给每个进程画的一个“大饼”。每个进程都认为自己拥有连续、巨大的内存地址空间(例如 64 位系统下理论有 128TB)。
  • 映射机制:内核通过 页表 (Page Table) 将进程需要的“虚拟地址”映射到真实的“物理内存地址”上。

即便没有 Swap,虚拟内存依然存在。 只是当物理内存耗尽时,系统没有了“退路”,只能通过 OOM Killer 强制杀掉进程来释放空间。


Swap(交换区)到底扮演什么角色?

Swap 是硬盘上的一块区域,它的本质是虚拟内存的物理背书扩展

  • 冷数据存放:内核会将那些长期不使用、但在内存里占座的“冷数据”移动到 Swap 中,从而腾出昂贵的物理内存给更活跃的进程或 Page Cache(文件缓存)
  • 内存过载的缓冲:当瞬时内存需求超过物理内存总量时,Swap 提供了一个缓冲地带,防止系统立即崩溃。

    为什么现在很多人建议关闭 Swap?

在高性能场景(如 Kubernetes、数据库)中,频繁的 Swap In/Out(换入换出) 会导致严重的磁盘 I/O 阻塞。硬盘的速度(即便是 NVMe SSD)也比 RAM 慢几个数量级,一旦进入“Swap 抖动”状态,系统响应会变得极慢,就像假死一样。


OOM Killer 的生存法则

当物理内存耗尽,且 Swap 也填满(或者没开)时,内核会启动 OOM (Out of Memory) Killer。它会根据一套评分机制(oom_score)选出一个进程并杀掉它。

  • 查看当前分值
cat /proc/[PID]/oom_score
  • 降低被杀优先级(范围 -1000 到 1000):
    # 极度保护进程,-1000 表示几乎不会被选中
    echo -1000 > /proc/[PID]/oom_score_adj

监控关键指标

不要只看 free -m 里的 used

  • Available 才是关键:内核会预留一部分内存给缓存,available 反映了在不触发交换的情况下,系统还能分配多少内存。
  • VSS vs RSS
    • VSS (Virtual Set Size):进程申请的所有虚拟内存。
    • RSS (Resident Set Size):进程实际占用的物理内存。

常见问题解答 (FAQ)

Q: 如果关闭 Swap,虚拟内存是不是全部不能用了?

A: 绝对不是。 进程依然通过虚拟地址运行。关闭 Swap 只是去掉了虚拟内存的“溢出保险箱”。
如果没有 Swap,物理内存用尽时,内核无法通过“页交换”来腾空间。此时,系统会变得非常“刚性”:要么分配成功,要么直接触发 OOM 杀进程。

Q: 到底该不该开 Swap?

桌面环境/开发机开。 它可以让你同时开启更多程序而不至于系统崩溃。
高性能生产环境 (K8s/DB)关(或设得很小)。 我们宁愿程序因为 OOM 快速失败并被容器重启,也不愿整个节点因为 Swap 抖动而陷入半死不活的长延时状态。


top 命令中的虚拟内存列

top 的默认视图中,你会看到两个核心指标:VIRTRES

  • VIRT (Virtual Image)
    • 定义:进程申请的所有虚拟内存总量。
    • 包含内容:进程的代码段、数据段、堆栈、使用的共享库(Shared Libraries),以及进程通过 malloc 申请了但尚未真正使用的内存。
    • 运维含义:VIRT 很大通常不需要惊慌,特别是 Java 应用或 Go 应用,它们启动时会预申请大量虚拟空间。但如果 VIRT 持续异常增长,可能存在内存泄漏的趋势。
  • RES (Resident Size)
    • 定义:进程当前实际占用的物理内存大小(不包含 Swap)。
    • 运维含义:这是最真实的指标,反映了进程“吃掉”了你多少内存条。
  • SHR (Shared Memory)
    • 定义:可以与其他进程共享的内存(如公共库 libc.so)。
    • 计算公式:实际物理内存占用 ≈ RES – SHR。

htop 命令中的虚拟内存列

htop 的界面更直观,它将这些指标细化为 VIRTRESSHR,以及一个非常有用的 MEM%

  • VIRT (Virtual Memory):与 top 一致。反映进程的“足迹”大小。
  • RES (Resident Memory):与 top 一致。反映进程实际占用的 RAM。
  • SHR (Shared Memory):反映该进程使用的共享库或共享内存段的大小。

htop 的独特视觉:底部的 Memory Bar

htop 顶部的进度条中,内存通常分为几种颜色(取决于版本):

  • 绿色:已使用的内存(Used)。
  • 蓝色:缓冲内存(Buffers)。
  • 黄色/橙色:缓存内存(Cache)。
  • 运维技巧:如果绿色条满了,说明物理内存告急;如果只有黄色/蓝色多,说明内存还在被内核高效地用于文件缓存。

为什么VIRT 往往比 RES 大很多

这是 Linux 内核的 “延时分配” (Lazy Allocation) 机制决定的。
当你执行 malloc(1GB) 时:

  1. 内核会在 VIRT 里给你记上一笔(发一张 1GB 的支票)。
  2. 只有当你真正往这块内存里写入数据时,内核才会触发“缺页异常”,在 RES 中分配真实的物理页面(兑现支票)。

运维实用建议

  1. 别被 VIRT 吓到:现代高性能服务器上,一个简单的程序 VIRT 达到几个 GB 是常态(特别是带有大量共享库或使用 mmap 映射大文件时)。
  2. 盯紧 RES 趋势:如果一个进程的 RES 随着时间推移线性增长,且在业务低谷期也不释放,那几乎可以判定存在内存泄漏。
  3. htop 的 F2 设置:建议在 htop 中按 F2 -> Columns,将 M_PSS (Proportional Set Size) 调出来。
    • PSS 会平摊共享库的内存占用。比如 10 个进程共享 10MB 的库,PSS 会给每个进程算 1MB。这比 RES 更能反映一个进程对系统内存压力的“真实贡献”。

vm开头的内核参数

以下是对你提供的 vm.* 参数的分类详细解释:

脏页与回写(Dirty Pages & Writeback)

这些参数控制内存中的“脏数据”(已修改但尚未写入磁盘的数据)如何同步到存储设备。

参数 解释
vm.dirty_ratio 脏页占系统总内存的最大百分比。达到此值时,生成脏页的进程将开始阻塞并主动将数据写入磁盘。
vm.dirty_background_ratio 脏页占总内存的百分比。达到此值时,内核后台线程(pdflush/kworker)开始异步将脏页写入磁盘。
vm.dirty_bytes / background_bytes 与上述比例参数类似,但直接指定字节数。若设为非 0,会覆盖对应的 ratio 参数。
vm.dirty_expire_centisecs 脏页在内存中驻留的最长时间(单位:1/100 秒)。达到此时间后会被标记为过期并等待回写。
vm.dirty_writeback_centisecs 内核后台线程唤醒并检查是否有过期脏页需要回写的周期(单位:1/100 秒)。
vm.dirtytime_expire_seconds 仅针对元数据(如时间戳)的脏时间过期设置,通常较长以减少 I/O。

交换与内存回收(Swapping & Reclaim)

控制系统在内存紧张时如何回收页面以及是否使用交换分区。

参数 解释
vm.swappiness 控制换出内存页到 Swap 的积极程度。值越高(最大 100)越倾向于使用 Swap;值越低越倾向于回收文件缓存(Cache)。
vm.vfs_cache_pressure 控制内核回收目录项(dentry)和索引节点(inode)缓存的倾向。默认 100,增大此值会加速回收 VFS 缓存。
vm.min_free_kbytes 强制 Linux 系统保留的空闲内存最小值(KB),用于处理原子分配等紧急情况。
vm.watermark_scale_factor 定义内存水位线(Watermark)之间的距离,影响内存回收的触发时机。
vm.zone_reclaim_mode 在 NUMA 架构下,决定是否在本地节点内存耗尽时回收缓存(0 表示优先从其他节点分配内存)。

内存过载与超发(Overcommit & OOM)

定义当内存请求超过物理限制时的内核行为。

参数 解释
vm.overcommit_memory 0: 启发式估算;1: 允许超发(永不拒绝);2: 严格遵循 overcommit_ratio
vm.overcommit_ratio overcommit_memory=2 时,允许分配的内存比例(物理内存 + Swap 的百分比)。
vm.panic_on_oom 当发生 OOM(内存溢出)时是否让系统直接崩溃(Panic)。0 表示启动 OOM Killer 杀掉进程。
vm.oom_kill_allocating_task 是否直接杀死触发 OOM 的那个进程。0 表示通过算法扫描并杀死分值最高的进程。
vm.oom_dump_tasks 发生 OOM 时是否打印系统当前的进程信息日志。

内存碎片与压缩(Compaction & Fragmentation)

优化内存布局,防止因碎片导致的大页分配失败。

参数 解释
vm.compaction_proactiveness 内存整理(碎片整理)的主动性(0-100)。值越高,内核越积极地在后台整理内存。
vm.extfrag_threshold 内存外部碎片触发的阈值,影响内核何时开始内存整理。
vm.compact_unevictable_allowed 是否允许压缩不可回收(Locked)的内存页。

其他关键参数

参数 解释
vm.max_map_count 一个进程允许拥有的最大内存映射区域(VMA)数量。某些数据库或大型游戏(如 Elasticsearch)需要调大此值。
vm.mmap_min_addr 允许用户空间程序进行 mmap 映射的最小内存地址。用于防止空指针解引用攻击。
vm.nr_hugepages 预留的常规大页(HugePages)数量,常用于数据库(如 Oracle/PostgreSQL)优化。
vm.page-cluster 控制一次从 Swap 读取或写入的页面数量($2^n$ 次方)。默认 3 表示一次处理 8 页。
vm.laptop_mode 是否开启笔记本模式。设为 1 会合并磁盘 I/O 以延长电池续航。
vm.admin_reserve_kbytes 为 sudo/root 用户预留的内存,确保即使内存耗尽,管理员仍能登录并处理问题。

/etc/security/limits.conf

as (Address Space): 限制进程的最大地址空间(虚拟内存总量)。

memlock: 限制用户可以锁定在物理内存中(不被交换到 Swap)的最大内存量。这在运行数据库(如 Oracle)或配置 HugePages 时非常重要。

rss (Resident Set Size): 限制进程在物理内存中驻留的最大值(注:在较现代的 Linux 内核中,此限制往往失效或不被严格遵守)。

stack: 限制进程堆栈的大小。

systemd related

systemd资源限制参数 (Control Group / cgroup)

Systemd 利用 Linux 内核的 cgroups 技术来限制进程的内存行为。以下是常见的参数:

参数 对应 sysctl/概念 解释
MemoryAmount 物理内存 限制该服务能使用的最大物理内存。
MemoryHigh 内存水位线 节流阈值。进程超过此值后,回收内存的压力会剧增,并会减缓进程执行速度。
MemoryMax OOM Killer 硬限制。如果进程试图使用超过此值的内存,将被立即杀死(OOM)。
MemorySwapMax Swap 限制 限制该服务可以使用的最大交换分区(Swap)额度。
MemoryLimit (旧版) 在旧版 systemd 中使用,等同于现在的 MemoryMax

systemd虚拟内存辅助参数

除了直接限制字节数,systemd 还可以配置进程的虚拟内存环境:

  • LimitAS=: 对应 ulimit -v。限制进程的虚拟地址空间(Address Space)大小。
  • LimitMEMLOCK=: 对应 ulimit -l。限制进程可以锁定在 RAM 中不被交换到 Swap 的内存大小。
  • LimitSTACK=: 对应 ulimit -s。限制进程的栈大小。
  • LimitRSS=: 限制最大驻留集大小(但在现代内核中通常无效)。

方法 A:直接编辑 .service 文件

/etc/systemd/system/my_service.service 中:

[Service]
ExecStart=/usr/bin/my_app
# 限制物理内存 2G,超过后强制杀死
MemoryMax=2G
# 限制虚拟地址空间 4G
LimitAS=4G
# 允许锁定内存(常用于数据库大页)
LimitMEMLOCK=infinity

方法 B:使用 systemctl 动态设置

无需重启服务,直接限制运行中的进程:

# 限制 nginx 服务最多使用 512M 内存
systemctl set-property nginx.service MemoryMax=512M

总结

Linux 的虚拟内存是一层精妙的抽象。

  • 虚拟内存是核心运行逻辑,永远存在。
  • Swap 是物理存储的延伸,是一种用性能换稳定性的权衡(Trade-off)。
  • OOM 是内核最后的自保手段,理解 oom_score_adj 能让你在资源极度匮乏时保住最重要的服务。

小贴士:在排查内存问题时,可以使用 vmstat 1 观察 si (swap in) 和 so (swap out) 两个字段。如果这两个数值持续很高,说明你的系统正在痛苦地挣扎。