Linux 交换空间常见问题详解
核心要点:本文档深入解析 Linux 内核中交换空间(Swap)的工作原理,澄清常见误解,并解答关于 Swap 使用的常见技术问题。
一、引言
凌晨时分,你的手机突然响起告警:
⚠️ 警告:系统 PROD01 交换空间利用率已超过 90%
如果你经常在深夜或周末被此类告警惊醒,或许需要重新审视高 Swap 利用率的告警设置。Swap 在现代内核中更多地是一种性能优化机制,而非系统危机的信号。本文将简要介绍 Swap 的工作机制,并解答相关常见问题。
二、核心术语与进程
2.1 内存相关术语
MemFree —— 查看于 /proc/meminfo,指当前完全未使用的内存量。虽然很多人希望看到较大的 MemFree 数值,但这实际上是浪费 RAM 资源的表现。
MemAvailable —— 可供程序使用的内存量,包括空闲内存和易回收的缓存(减去预留缓冲区)。同样存在于 /proc/meminfo 中。
SwapFree —— 已从 RAM 换出到交换磁盘的闲置进程内存页面。SwapFree 表示交换磁盘上剩余的未使用空间,也存在于 /proc/meminfo。
2.2 内核线程
kswapd —— 处理内存回收和交换的内核线程。它根据多个复杂因素,从 RAM 中换出最近最少使用(LRU)的页面——无论来自页面缓存还是进程内存。
kcompactd —— 另一个内核线程,负责将较小的空闲内存块合并为较大的物理连续块,从而减少内存碎片。
2.3 水位线阈值
Low watermark(低水位线) —— 控制 kswapd 何时被唤醒进行后台回收的阈值。可在 /proc/zoneinfo 中查看。
Min watermark(最低水位线) —— 控制分配进程本身在分配时阻塞的阈值,用于回收内存(也称为直接回收)。同样可在 /proc/zoneinfo 中查看。
High watermark(高水位线) —— 控制 kswapd 何时重新进入睡眠状态、内存回收被视为成功的阈值。可在 /proc/zoneinfo 中查看。
三、MemFree、kswapd 与后台回收
系统启动后,大部分内存处于 MemFree 状态。随着程序分配内存,MemFree 逐渐减少。Linux 内核的设计理念是尽可能使用可用内存来提升系统和应用程序性能。这意味着 MemFree 处于低位是正常的、健康的。大多数空闲内存用于缓存——任何能避免磁盘 I/O 的操作都有利于应用程序性能。
一般来说,用于页面缓存的内存很容易回收。由于页面缓存中的数据(大部分情况下)也存在于磁盘上,这些页面在内存压力下可以轻松回收和重用。
当 MemFree 低于低水位线时,kswapd 被唤醒并尝试回收内存。一旦空闲内存达到高水位线,它就会停止回收工作。
如果 MemFree 继续低于最低水位线,不仅 kswapd 会尝试回收内存,试图分配内存的各个进程也会尝试回收。这时你可能会注意到一些应用程序延迟增加,因为每次分配请求在满足之前都需要进行一些直接回收。
这些流程中回收的页面可以是匿名页面(即进程堆栈)或文件页面(即页面缓存页面)。一些内核内存(例如 slab 缓存)也会在这里被回收,但这通常不是主要收益来源,因此本文将忽略 slab 缓存回收器。
四、常见问题解答
4.1 Swap 是否必需?能否直接禁用?
启用交换空间为系统提供了一种换出非磁盘支持页面的方式。如果存在分配突发,启用 Swap 可以让系统在决定换出哪些页面时具有更大的灵活性。如果没有 Swap 而系统处于内存压力下,恢复内存的唯一方式就是换出页面缓存。即使存在具有大量非活跃匿名内存的闲置进程,它们也不会被换出,因为它们没有磁盘支持——这些页面没有后备存储。
例如,如果系统上运行大量进程(因此有大量匿名内存使用),同时有中等程度的页面缓存使用,且内存分配请求突然激增,那么回收算法将受到页面缓存量少的限制。如果页面缓存也变得"热门"(由于备份应用程序或其他文件 I/O 密集型应用程序),系统将开始抖动——仅换出页面缓存后又立即读回。这是不稳定的,可能导致内存不足(OOM)杀手被触发。
没有必要让冷门的匿名页面一直驻留在内存中——启用交换空间让内核可以在文件页面和闲置进程内存之间选择换出对象,根据工作负载的内存访问模式。这将带来所有应用程序的最佳性能,以及最佳的资源(即内存)利用率。
另一方面,在某些系统上,正常工作负载期间的交换可能会增加其他进程的延迟。在大多数情况下,这种影响微乎其微。但对于延迟敏感或实时应用程序,这可能是个问题——大多数工作负载不属于这一类别。
4.2 为什么 Swap 使用率很高,但现在系统并未主动交换?SwapFree 不应该增加吗?
/proc/meminfo 中的 SwapFree 是当前未使用的交换空间量。随着系统运行时间增加,SwapFree 通常会减少;如果过去存在内存压力(无论是前一天还是一个月前)导致页面被换出,这些页面通常会保留在交换空间中,除非拥有它们的进程死亡,或者页面在内存中被修改(这会使交换副本过期,从而被丢弃)。
因此,即使系统当前没有内存压力,SwapFree 也可能由于历史交换而处于相当低的水平。如果你没有看到主动交换(在 vmstat 输出的 si 和 so 列中),通常对系统的交换 I/O 影响很小,尽管回收延迟可能出于其他原因存在。
如果你看到抖动,即页面由于内存压力被换出,但因其被 активно 引用而又被换回,这就是一个问题,应该解决——可能通过平衡系统上的工作负载或添加更多内存。
4.3 为什么内存充足的情况下 Swap 使用率仍然很高?
这取决于水位线。可能全局有很多空闲内存,但它们在 NUMA(Non-Uniform Memory Access,非均匀内存访问)节点之间分配不均,一个 NUMA 节点处于内存压力边缘,另一个则资源利用不足。如果发生这种情况,内存不足的 NUMA 节点可能开始交换(取决于分配策略、cpusets/mempolicy 约束和区域回收行为)。
以下是一个 NUMA 不平衡情况的示例,使用 numastat 数据说明:
每节点系统内存使用量(单位:MB): Node 0 Node 1 Total --------------- --------------- ---------------MemTotal 772243.23 774101.36 1546344.59MemFree 9418.90 106649.16 116068.06 <--...Active(file) 81925.87 56966.03 138891.89Inactive(file) 245829.25 168282.12 414111.36...FilePages 333708.79 236806.75 570515.54...
在这里,NUMA 节点 0 只有约 9 GB 空闲,而 NUMA 节点 1 有超过 100 GB 空闲。这是明显的失衡,如果节点 0 的内存压力恶化,节点 0 的页面可能会被换出,尽管 MemFree > 125 GB(几乎全部来自节点 1)。
以下是另一个更极端的示例:
Node 0 Node 1 Total --------------- --------------- ---------------MemTotal 1547405.20 1548190.00 3095595.20MemFree 13432.87 848495.85 861928.72 <--...Active(file) 864159.89 31523.51 895683.39 <--Inactive(file) 9656.17 9037.02 18693.19...MemFree 0.87% 54.81% 27.84%MemUsed 99.13% 45.19% 72.16%
几乎有 850 GB 空闲,但全部在 NUMA 节点 1 上。节点 0 将很快面临内存压力,系统有多种策略来处理这种情况:
交换只是其中很小的一部分,并不是最重要的。所有其他策略也在部署中,目标是尽可能高效地回收尽可能多的内存。
4.4 为什么系统 Swap 频繁,即使页面缓存很充足?难道不应该先从页面缓存回收吗?
简短回答:两者同时进行。内核从文件页面(即页面缓存)和匿名进程页面(通过将它们换出到交换空间)回收内存。它更倾向于回收干净的页面缓存,因为这些可以无需 I/O 即可轻松重用。但它不会等到页面缓存耗尽才开始交换——两组页面都会被回收,尽管回收速率不同。
技术特点:内核代码通过 swappiness 参数平衡匿名页面和文件页面的回收压力。当 swappiness 为 100 时,匿名和文件具有相同的 I/O 成本。
如果非活跃文件页面数量较少,或者活跃文件页面有较高的回填率,回收偏好将倾向于匿名页面——基本上是系统能以最小性能成本和 I/O 成本回收的页面。启动交换没有单一触发条件。
让我们观察一个系统上的行为:
交换前:Cached: 40329468 kBSwapFree: 25165820 kB页面缓存使用量激增后:Cached: 225038676 kBSwapFree: 17709564 kBCached: 226585112 kBSwapFree: 8473852 kBCached: 245398456 kBSwapFree: 7650044 kB
在这里,我们看到页面缓存使用量在午夜到凌晨 2 点之间急剧增加,可能是由于计划运行的备份应用程序。这会增加系统上的内存压力,必要时系统可能开始交换。将此类备份进程添加到受内存约束的 cgroup 将确保它不会消耗系统上的所有可用内存,从而不影响其他进程。
4.5 页面如何从活跃 LRU 降级到非活跃 LRU?
Linux 将页面分为两类:匿名页面(非文件支持的,例如堆栈等)和文件页面(RAM 中有磁盘后备文件的页面,例如库、数据文件等)。
这两类进一步使用 LRU(最近最少使用)算法分为两个列表:活跃列表包含最近被引用的页面,非活跃 LRU 列表包含一段时间未被访问的页面。如果访问了非活跃 LRU 中的页面,它会被"提升"到活跃列表。内存回收优先从非活跃 LRU 列表中选取页面,因为换出正在使用的页面效率不高。同样,换出干净的文件支持页面更容易,因为它们在磁盘上已是最新状态,而回收脏文件支持页面(需要先写出)或匿名页面(也需要写出到交换空间)则更复杂。
shrink_active_list() 函数将页面从活跃列表移动到非活跃 LRU 列表;shrink_active_list() 由 kswapd 在页面回收期间调用。通常在非活跃列表太小时调用此函数,通过将页面停用,为回收算法提供足够的候选页面以取得进展。
它扫描活跃列表末尾的一批页面(由 nr_to_scan 表示),如果可以降级,就将它们移动到非活跃列表。如果无法停用,页面会被旋转回活跃列表头部——这给页面一次额外的机会在活跃列表中循环,然后再次检查是否可以降级。
注意事项:/proc/vmstat 中可以读取跟踪每次 pass 中停用页面数量的计数器(即 pgdeactivate)。这是一个累积的全局计数器,跟踪系统运行期间的所有停用操作。如果你监控此文件并看到 pgdeactivate 上升,表明系统由于缺乏足够的非活跃页面进行回收而正在收缩活跃列表。
五、MemAvailable 的准确性
5.1 MemAvailable 是否准确反映系统可用内存?
并非总是如此。MemAvailable 是一个启发式值,对于某些工作负载可能不准确。
long si_mem_available(void){ /* * 并非所有页面缓存都可以释放,否则系统将开始交换。 * 假设至少一半的页面缓存,或低水位线价值的缓存,需要保留。 */ pagecache = pages[LRU_ACTIVE_FILE] + pages[LRU_INACTIVE_FILE]; pagecache -= min(pagecache / 2, wmark_low); available += pagecache; ...}
MemAvailable 只是一个启发式值——计算假设至少一半的页面缓存可以轻松回收,但如果页面缓存是"热门"的,这并不成立。例如:
MemAvailable: 204435292 kBActive(file): 185688096 kBInactive(file): 11493160 kB
这里大部分页面缓存是"活跃"或"热门"的。
六、内核如何决定换出哪些进程?
进程不会被选择换出——相反,页面是根据大量参数被选择换出/驱逐,包括最近一次访问/引用的时间。回收算法青睐回收成本较低的页面。文件支持页面的回收成本取决于它是干净的还是脏的(脏页面在换出前必须写出到磁盘,由于额外的 I/O 使其回收成本高昂),加上回填成本(页面被驱逐但需要再次使用,必须从交换空间读回,产生更多 I/O)。对于没有文件支持的进程页面(即匿名页面),在回收前无法避免 I/O——它们必须被写出到交换空间才能被释放。回填率(即从交换空间读回)也增加了匿名页面的回收成本。换出哪个进程的匿名页面无关紧要——重要的是该页面已有一段时间未被引用,因此被驱逐以便内存在其他地方重用。
七、内核升级后的 Swap 变化
7.1 升级到 Oracle UEK7 后 Swap 增多
上游内核(5.15 版)中合并了一些影响回收行为和 Swap 使用的优化,包括:
mm: vmscan: limit the range of LRU type balancingmm: vmscan: reclaim writepage is IO costmm: vmscan: determine anon/file pressure balance at the reclaim rootmm: balance LRU lists based on relative thrashingmm: only count actual rotations as LRU reclaim costmm: deactivations shouldn't bias the LRU balancemm: base LRU balancing on an explicit cost modelmm: vmscan: drop unnecessary div0 avoidance rounding in get_scan_count()mm: remove use-once cache bias from LRU balancingmm: workingset: let cache workingset challenge anonmm: allow swappiness that prefers reclaiming anon over the file workingsetmm: keep separate anon and file statistics on page reclaim activitymm: fix LRU balancing effect of new transparent huge pages
这改变了内存回收行为,使得 Swap 使用不再是压力下系统的最后手段——而是正常回收流程的一部分,特别是如果页面缓存被频繁回填。这个内核.org 补丁集使系统更多地利用交换空间,即使在"正常"内存压力下,如果页面缓存是热门的话。
让我们查看一些来自 /proc/vmstat 的统计数据:
workingset_nodes 1265381workingset_refault_anon 201574workingset_refault_file 181043188workingset_activate_anon 79184workingset_activate_file 25598943workingset_restore_anon 2066workingset_restore_file 7334788workingset_nodereclaim 171392...pswpin 201575pswpout 4522469...pgsteal_kswapd 246507286pgsteal_direct 253820pgdemote_kswapd 0pgdemote_direct 0pgscan_kswapd 269136889pgscan_direct 278885pgscan_direct_throttle 0pgscan_anon 26736135pgscan_file 242679639pgsteal_anon 4493398pgsteal_file 242267708
观察要点:
workingset_refault_file 很高——表明页面缓存是热门的,被回收后以高频率回填。这将增加对匿名 LRU 列表的压力(因此也增加交换)。workingset_restore_file 跟踪即将被回收的文件被恢复到工作集的频率(由于页面在驱逐前再次被引用)——这个值很高,表明非活跃文件 LRU 列表中的许多页面并不是好的回收候选,它们很快被提升到活跃列表。这两个计数器都表明热门页面缓存不适合回收。- kswapd 活动很高(由
pgsteal_kswapd、pgscan_kswapd 表明)。 - 直接回收似乎较少,这是好的(
pgscan_direct、pgsteal_direct)。
八、OOM 杀手相关
8.1 Swap 使用率接近 100% 会触发 OOM 吗?
通常不会。高的 Swap 利用率本身不会触发 OOM;重要的是当前分配是否能取得回收/压缩进展。如果页面缓存中有足够的可回收内存,OOM 就不太可能,但有例外(例如 memcg OOM、严格的 cpuset/mempolicy 约束或高阶/GFP 约束)。
OOM 杀手只在内核多次尝试回收内存以满足分配请求且失败后才会被调用。__alloc_pages_slowpath() 是处理无法立即满足的分配的函数。该函数唤醒 kswapd,尝试回收内存,然后压缩它(以确保内存对于更高阶分配不会太碎片化),然后重试分配。
从上面的代码片段和注释可以看出,内核非常努力地满足分配请求。只要后台回收(由 kswapd 进行)取得一些进展,内核就会尝试压缩和分配。只要页面缓存使用的内存正在被回收,它就非常可用来被回收——尽管很慢。如果交换空间满了,系统将不再能够优化文件 I/O,并将开始驱逐页面缓存——甚至将脏页面写出到磁盘以便驱逐,并收缩 slab 缓存等。只有在所有选项都用尽时,它才会调用 OOM 杀手。在这种情况下,整个系统因内存不足而陷入僵局,无法取得进展,它会选择一个"受害"进程并杀死它,希望释放足够的内存来缓解压力,让其他进程继续运行。
简而言之,如果回收/压缩不断取得进展,OOM 就不太可能。如果页面缓存几乎被完全驱逐,交换空间 100% 满,空闲内存低于每区域低水位线(或系统过于碎片化且压缩失败)——那就是系统陷入困境的时候,OOM 杀手就会介入。
九、何时应该关注交换?
9.1 什么时候交换是坏事?
当系统正在交换时,它通常被视为内存压力的首要症状之一,预示着更糟糕的事情将要发生。这是一个神话。也许这就是过去内存稀缺、磁盘非常慢时交换的使用方式,系统会积极尝试不将数据分页到磁盘,除非别无选择。
现在情况不同了。随着 TB 级内存和 SSD 越来越普遍,交换不再是一个必要的恶魔——它只是确保系统以最高效率运行的机制之一。内存回收选择最佳候选页面以获得最佳系统性能,在许多情况下将活跃页面缓存保留在内存中,同时将闲置页面换出到交换空间。
即使交换空间已用完 100%,也没有理由惊慌(请参阅上一个问题)。只有当持续不断地换入和换出——也就是抖动——系统才有麻烦。抖动发生是因为可用 RAM 不足以满足工作负载的内存需求。由于持续的内存压力,页面被换出到磁盘,但这些仍然是活动工作集的一部分。进程继续读写这些页面,这导致它们再次被换入,这增加了内存压力(因为这些新页面必须去某个地方),导致其他活动页面被换出,以此类推。这将导致所有应用程序性能下降,系统将慢慢陷入停顿。
十、内核可调参数
10.1 影响交换的内核 sysctl 参数
有几个影响内存回收行为的旋钮——这间接影响交换。
vm.swappiness
这是主要的交换相关可调参数,控制内核回收文件页面与匿名内存的积极程度——后者被换出到交换空间。值可以从 0 到 200,默认值为 60。降低 vm.swappiness 通常会使回收算法更倾向于页面缓存回收而不是交换,而增加这个值则更倾向于将进程内存换出到交换空间并保留页面缓存更长时间。
注意事项:在某些工作负载上设置 vm.swappiness=0 可能会增加 OOM 风险,因为会大大减少回收的匿名页面。
vm.min_free_kbytes
这直接影响区域水位线值——控制内核留出多少"预留"内存——即普通分配无法使用这个池。这反过来又影响内存回收何时开始以及运行多长时间,这将影响多少交换空间被利用。如果这个值设置得太低,系统将没有足够的预留内存来处理分配突发。内核将难以进行回收、压缩、脏页面写出等。甚至内存回收流程可能需要分配内存(例如迁移页面),如果这个设置太低,它将停滞(在直接回收中)或失败。另一方面,如果设置太高,所有这些内存都被留作预留,不能用于常规分配。此外,这增加了区域水位线,意味着 kswapd 将运行更长时间,从内存中驱逐更多页面,也交换更多。除非有证据表明当前设置对工作负载 suboptimal,否则不建议调整此设置。
vm.compaction_proactiveness
此旋钮控制后台压缩的积极程度,由 kcompactd 执行。如果压缩成功,这将减少系统上的碎片,从而增加更高阶分配请求成功的可能性。这将有助于减少 kswapd 的运行时间。
watermark_boost_factor 和 watermark_scale_factor
与 vm.min_free_kbytes 类似,它们影响区域水位线,改变回收积极程度,进而可能增加交换使用。
vm.drop_caches
此 sysctl 不是可调参数,但写入它将执行特定的操作以减少系统上的内存压力。
写入 1 将删除可回收的页面缓存。写入 2 将释放可回收的 dentry 和 inode slab 缓存。写入 3 将同时释放 slab 缓存和页面缓存内存。
⚠️ 警告:请勿在生产系统上执行此操作而不了解性能影响。在工作负载繁重且正在积极使用所述缓存的系统上删除页面缓存并不是一个好主意。这是一把钝锤子,无论应用程序的性能影响如何,都会驱逐活跃和非活跃页面。其他考虑因素:
- 在实际内核处理 drop_caches 期间,系统可能会经历 browned 或驱逐。
- 它释放的任何内存通常是暂时的——如果工作负载需要,系统会将所有这些页面重新读回。如果系统上存在真正的问题,它不会修复。
- 由于这些被驱逐页面的回填,一些应用程序在页面缓存被删除后可能会看到更高的延迟或性能下降。
影响内存回收行为从而影响交换积极性的内核可调参数还有很多。在更改默认值之前,请仔细考虑所有这些可调参数的优缺点。在没有专家建议的情况下更改这些可能会产生令人惊讶的不良后果。
十一、如何减少 Swap 使用?
11.1 减少 Swap 使用的建议
减少 Swap 使用本身并不是一个值得追求的目标,除非存在一些负面影响或性能问题。通常,在一个规划良好的系统中,工作负载不超过内存容量,不应该有 heavy 的交换或抖动。如果你只是偶尔看到一些 Swap 使用,那是完全正常的。如果 SwapFree 持续下降(并保持低位),这也是正常的。
如前所述,以下是一些可能导致健康系统上交换增加的因素:
- 如果页面缓存很热门,并且主要用于一次性读取的文件(如备份或安全扫描仪),请考虑对这些应用程序使用 cgroup 限制,以使它们不会使用太多内存
⚠️ 警告:请勿运行 swapoff -a 和 swapon -a 来尝试释放 Swap。这没有帮助。如果系统已经内存不足,而你关闭了 Swap,你将强制将已换出的数据读回内存,这可能导致崩溃/重启。
尝试增加 SwapFree 使其更接近 SwapTotal 没有任何优势。如果启用了 Swap,尝试不使用它也没有好处。让内核决定哪些页面保留在内存中、哪些页面驱逐,这对整体系统性能有好处。除非遇到性能问题或系统正在抖动,否则无需尝试更改该算法。
十二、Linux Swap 最新更新
12.1 多代 LRU(MGLRU)
在较新的内核中,可以使用多代 LRU(MGLRU)而不是两个 LRU 列表(活跃和非活跃),具体取决于内核版本、配置和运行时设置。MGLRU 在 5.x 系列进入上游并在 6.x 继续发展,目标是改进页面回收。这根据页面最近被访问的时间将页面分类为多代——较旧代中的页面最近未被访问,而较新代中的页面更活跃。然后内核可以更高效地回收旧页面,并显著减少回填率。基准测试表明这可以提高 kswapd 的效率,以及显著减少工作集回填。从理论上讲,这应该减少不必要的交换,因为在识别真正不活跃/闲置页面方面有更好的准确性。
请注意,这不会消除交换;例如,如果最老代中的大多数页面属于闲置进程,而页面缓存页面位于较新的一代中,那些进程页面将首先被驱逐——即交换——然后才是回收页面缓存页面。这对系统整体来说仍然是最优的事情。这里的目标是从内存中驱逐真正冷的页面,MGLRU 在识别这些页面方面带来更高的准确性,从而减少被驱逐页面的回填/换入。
十三、结论
核心要点:本文档表明,Swap 使用不再是系统问题的风向标。交换空间正被用于通过释放真实内存供活跃进程和页面缓存使用来提高性能。监控空闲 Swap 或已用 Swap 的告警应更改为查找增加且接近恒定的主动交换周期——即 vmstat 中的 si 和 so 字段。如果你没有看到这种情况且没有性能问题,就 Swap 空间使用而言没有什么可担心的。单独的 SwapFree 低本身并不是灾难即将来临的信号。默认内核 sysctl vm.swappiness 在可能的情况下确实更倾向于回收页面缓存而不是交换,但同时交换将与页面缓存驱逐并行进行。如果你想减少 Swap 使用,请考虑使用 cgroup 限制来约束备份等应用程序,使它们不会为文件数据缓存使用所有可用内存。
附录:推荐监控指标
可通过以下命令监控系统交换行为:
# 查看内存和交换状态vmstat 1# 监控交换活动(si: 换入,so: 换出)vmstat 1 | awk '{print "si: "$7, "so: "$8}'# 查看详细的内存信息cat /proc/meminfo | grep -E "Swap|Mem"# 查看 vmstat 计数器cat /proc/vmstat | grep -E "pgsteal|pgscan|workingset|pswp"
文档来源:Linux Swapping FAQ
原始作者:Aruna Ramakrishna、John Sobecki
本文由 AI 助手整理优化,欢迎关注、分享转载,请注明出处