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 的默认视图中,你会看到两个核心指标:VIRT 和 RES。
- VIRT (Virtual Image):
- 定义:进程申请的所有虚拟内存总量。
- 包含内容:进程的代码段、数据段、堆栈、使用的共享库(Shared Libraries),以及进程通过
malloc申请了但尚未真正使用的内存。 - 运维含义:VIRT 很大通常不需要惊慌,特别是 Java 应用或 Go 应用,它们启动时会预申请大量虚拟空间。但如果 VIRT 持续异常增长,可能存在内存泄漏的趋势。
- RES (Resident Size):
- 定义:进程当前实际占用的物理内存大小(不包含 Swap)。
- 运维含义:这是最真实的指标,反映了进程“吃掉”了你多少内存条。
- SHR (Shared Memory):
- 定义:可以与其他进程共享的内存(如公共库
libc.so)。 - 计算公式:实际物理内存占用 ≈ RES – SHR。
- 定义:可以与其他进程共享的内存(如公共库
htop 命令中的虚拟内存列
htop 的界面更直观,它将这些指标细化为 VIRT、RES、SHR,以及一个非常有用的 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) 时:
- 内核会在 VIRT 里给你记上一笔(发一张 1GB 的支票)。
- 只有当你真正往这块内存里写入数据时,内核才会触发“缺页异常”,在 RES 中分配真实的物理页面(兑现支票)。
运维实用建议
- 别被 VIRT 吓到:现代高性能服务器上,一个简单的程序 VIRT 达到几个 GB 是常态(特别是带有大量共享库或使用
mmap映射大文件时)。 - 盯紧 RES 趋势:如果一个进程的 RES 随着时间推移线性增长,且在业务低谷期也不释放,那几乎可以判定存在内存泄漏。
- 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) 两个字段。如果这两个数值持续很高,说明你的系统正在痛苦地挣扎。
