当前位置:首页 > 技术学院 > 技术前线
[导读]无论是个人电脑还是服务器,物理内存的容量总是有限的——当运行的程序越来越多,物理内存被占满之后,操作系统该怎么办?直接拒绝新的内存请求?还是杀掉老进程?Linux给出的解决方案是‌内存交换机制‌:把暂时不用的内存数据写到磁盘上的交换区,腾出来物理内存给需要的进程用,需要用到这些数据的时候再换回来,这样就相当于“凭空多出来”一块虚拟内存,能支撑更多程序运行。很多开发者只知道swap分区,却不清楚内存交换到底是怎么运行的,什么时候会触发交换,交换又会对系统性能产生什么影响?搞懂内存交换的逻辑,才能更好地排查系统卡顿、OOM这类常见问题。

无论是个人电脑还是服务器,物理内存的容量总是有限的——当运行的程序越来越多,物理内存被占满之后,操作系统该怎么办?直接拒绝新的内存请求?还是杀掉老进程?Linux给出的解决方案是‌内存交换机制‌:把暂时不用的内存数据写到磁盘上的交换区,腾出来物理内存给需要的进程用,需要用到这些数据的时候再换回来,这样就相当于“凭空多出来”一块虚拟内存,能支撑更多程序运行。很多开发者只知道swap分区,却不清楚内存交换到底是怎么运行的,什么时候会触发交换,交换又会对系统性能产生什么影响?搞懂内存交换的逻辑,才能更好地排查系统卡顿、OOM这类常见问题。

先理清基础:为什么需要内存交换,它解决了什么问题

内存交换不是凭空出现的,它本质是虚拟内存管理的一部分,核心解决两个问题:

第一,‌物理内存不足的扩容需求‌。程序实际用到的内存总量超过物理内存容量的时候,总不能直接让新程序无法启动,或者杀掉已有进程吧?内存交换把不常用的内存页换到磁盘,把物理内存让给急需的进程,相当于用磁盘空间扩展了内存容量,让系统能运行更多程序。

第二,‌闲置内存的高效利用‌。有些程序启动之后,大部分时间都处于挂起状态,占着物理内存却不用,把这部分数据交换到磁盘,把物理内存留给更需要的活跃程序,能提升整体内存利用率,让系统运行更流畅。

第三,‌匿名页的回收支持‌。Linux中,程序申请的堆内存、栈内存这类没有对应文件的匿名内存,如果暂时不用,没办法直接从内存中丢弃(因为没有文件存备份),只能交换到交换区保存,需要的时候再换回来,所以内存交换是匿名页回收的基础,没有交换就没法回收闲置的匿名内存。

我们可以这么理解:物理内存就像酒店的前台大堂,只能放得下当前正在办理入住的客人,那些暂时不办事的客人,就请到门口的休息区(交换区)等着,需要的时候再叫进来,这样大堂永远有空间给新客人,不会因为人太多挤垮整个酒店,这就是内存交换的核心作用。

内存交换的核心原理:换出与换入,两步完成空间腾挪

内存交换的整个流程非常清晰,分为换出和换入两个核心过程,我们一步步拆解:

第一步:当内存不够的时候,触发换出操作

当系统需要分配新的内存页,但是物理空闲内存已经低于阈值的时候,内核的内存管理模块就会触发内存回收,把不常用的内存页换出到交换区,腾出物理内存:

筛选可以换出的页‌:不是所有内存页都能换出,内核会先筛选:

已经映射到磁盘文件的文件页,如果是干净的(没有被修改过),直接丢弃就行,不需要写到交换区,需要的时候再从原文件读出来就行,只有脏的文件页才需要写回磁盘;

匿名内存页(堆、栈、进程私有数据)没有对应文件,必须要写到交换区才能释放物理内存;

内核态的内存、正在被使用的内存页,一般不会被换出。

给选中的页分配交换空间‌:内核会在交换分区或者交换文件里找一块空闲的空间,把要换出的内存页数据写到这块空间里。

记住每一页对应的交换空间位置,存在页表项里,把原来的物理内存地址标记为无效,然后把这块物理内存释放出来,加到空闲内存链表中,给新的内存分配用。

换出完成之后,原来的进程还以为自己的数据还在内存里,只是页表已经记录了数据现在在交换区,对进程完全透明,进程根本不知道自己的数据已经被换到磁盘上了。

第二步:当进程访问被换出的页时,触发换入操作

当进程要访问已经被换出到交换区的内存页的时候,CPU会触发一个缺页异常,内核接手处理这个异常,然后把数据从交换区读回到物理内存,这个过程就是换入:

进程访问虚拟地址,页表发现这个页已经被换出到交换区,触发缺页中断;

内核处理缺页中断,找一块空闲的物理内存(如果现在没有空闲内存,就再重复一次换出流程,先腾出一块空闲内存);

根据页表项里记录的交换位置,把数据从交换区读回到刚腾出来的物理内存;

更新页表项,把物理地址填进去,标记为有效,然后退出中断,进程继续执行,就好像什么都没发生一样。

整个换入换出过程对应用程序完全透明,程序根本感知不到数据被换出过,所以用户只会觉得“内存比物理容量大”,不会感觉到中间的交换操作,这就是虚拟内存设计的巧妙之处。

交换不是越大越好:交换对性能的影响

很多新手以为,交换越大,系统能支持的内存越多越好,其实不对,交换本质是“用磁盘空间换内存容量”,磁盘的速度比内存慢几百上千倍,所以交换会对系统性能产生非常大的影响,我们分两种情况看:

轻度交换:对性能影响很小,甚至能提升效率

如果只是偶尔把非常少量的闲置内存换出去,大部分活跃数据都还在内存里,这时候交换对性能几乎没有影响:那些被换出去的数据本来就是长期不用的,腾出来物理内存给文件做缓存,反而能提升IO性能,这时候交换是正向的。

比如服务器物理内存是16G,实际只用了12G,剩下4G空闲,系统偶尔会把一两个长期闲置的后台进程内存换出去,把这部分内存用来做文件缓存,整体性能反而更好,这就是合理利用交换的场景。

重度交换:频繁换入换出,系统直接卡死

如果物理内存真的严重不足,大量数据都要放到交换区,进程运行的时候需要频繁换入换出,这时候就会出现“交换风暴”:磁盘IO被占满,CPU花大量时间等待IO完成,系统几乎没法正常运行,俗称“卡爆了”。

我们算一下速度:内存的读写速度大概是几十GB每秒,普通机械硬盘的读写速度大概是100MB每秒,差了几百倍;就算是SSD,速度也只有几百MB每秒,比内存还是差几十倍。如果一个进程每访问几次内存就要缺一次页,每次缺页要几毫秒读磁盘,那原本一秒钟能完成的计算,现在要几分钟,系统自然就卡死了。

这就是为什么很多生产环境会建议关闭交换,或者把交换阈值调得很低:就是怕内存不够的时候触发大量交换,直接把系统卡崩,还不如直接触发OOM杀掉一两个进程,保住整个系统可用。

Linux内存交换的关键设计:从swap分区到zram,不同方案的取舍

Linux发展这么多年,内存交换有多种不同的实现方案,不同方案适合不同场景,我们看看常见的几种:

传统方案:独立交换分区(swap partition)

最经典的方案就是在安装系统的时候,分一块独立的磁盘分区作为交换区,内核直接读写这个分区,不需要经过文件系统,速度比交换文件快一点,是服务器和桌面系统最常用的方案。

传统的设计中,一般建议交换分区大小是物理内存的1-2倍,但现在物理内存越来越大,服务器一般配8G或者16G交换就够了,不需要真的配两倍物理内存,配太大也是浪费磁盘空间。

灵活方案:交换文件(swap file)

除了分区,Linux也支持用普通文件作为交换区,就是在已有文件系统里创建一个大文件,把它作为交换空间用。这种方案的好处是灵活,不需要重新分区,想要多大就创建多大,不需要的时候直接删掉就行,适合已经装好系统,想要临时增加交换空间的场景。

交换文件的性能比交换分区略差一点,因为要经过文件系统的地址映射,多了一层开销,但现在SSD速度足够快,这点差距几乎感知不出来,现在很多桌面发行版默认都用交换文件了,更方便管理。

嵌入式/移动场景:压缩交换zram

zram是现在很多移动设备、嵌入式设备常用的交换方案,它不是把交换数据写到磁盘,而是在内存里划出一块区域,把要交换的内存页压缩之后存在这里,相当于用压缩换容量:本来100MB的闲置数据,压缩之后可能只有30MB,这样就能省出70MB的物理内存,整个交换都在内存里,不需要读磁盘,速度比磁盘交换快很多。

zram的缺点是需要CPU做压缩解压,会占用一点CPU资源,但是现在CPU性能足够,用一点CPU换更多可用内存,在移动设备上非常划算,所以现在安卓手机基本都用zram作为交换方案,很多轻量Linux发行版也默认开启zram。

不过zram本质还是用内存做交换,压缩之后虽然省空间,但还是占用物理内存,如果真的内存严重不足,压缩之后也不够用,还是会触发OOM,适合内存不算特别大,但又不想用磁盘交换拖慢速度的场景。

混合方案:zram+swap

现在很多发行版会用混合方案:默认把交换放在zram里,利用压缩省内存,zram满了之后再把不常用的交换页写到磁盘swap,兼顾了速度和容量,适合内存不大的个人电脑,既有不错的速度,又能应对偶尔的内存不足场景。

实际生产中,内存交换的常见问题和优化思路

理解了内存交换的原理,我们就能解决很多实际生产中遇到的问题:

问题一:服务器OOM了,到底要不要开交换?

这个问题其实分场景:

如果是内存足够的服务器,开一点交换没问题,应对突发的内存峰值,比直接OOM杀掉进程好,哪怕卡一会儿也比直接崩溃强;

如果是内存本来就不够,业务对延迟要求很高,那建议关闭交换,或者把swappiness调得很低(比如调到10以下),让内核尽量不换出,内存不够的时候直接OOM,杀掉一两个进程,比整个系统卡死好;

如果是数据库这种对内存延迟要求极高的服务,建议直接关闭交换,交换带来的延迟波动会严重影响数据库性能,宁愿少跑点业务,也不要让交换拖慢整个服务。

问题二:系统为什么会突然卡顿,怎么看是不是交换导致的?

如果系统突然卡顿,磁盘IO很高,首先可以用free -h看一下:如果used内存占满,swap used已经很高,再用vmstat看si(换入)和so(换出)列,如果这两个值持续大于0,说明正在频繁换入换出,就是交换导致的卡顿。

这时候最好的解决方式就是加物理内存,或者调整业务,关掉一些不用的进程,释放内存,交换只是应急方案,不能解决根本的内存不足问题。

问题三:swappiness参数到底是什么意思,怎么调?

swappiness是Linux内核用来控制交换倾向的参数,范围是0-100,值越大,内核越倾向于换出内存,值越小,越尽量不换出。默认值是60,也就是内存用到一定程度就开始换出。

对于服务器来说,一般建议调到10或者更低,告诉内核尽量只用物理内存,不到万不得已不要换出,避免突发交换导致卡顿;如果是个人电脑,内存不大,可以用默认值60,或者调到30,兼顾容量和性能。

问题四:为什么关掉交换之后,系统反而更流畅了?

很多人都有这个体验:关掉交换之后,系统反而更快了,这是为什么?因为如果你的物理内存足够,根本不需要交换,内核反而会把一些不常用的内存换出去,等到用到的时候再换进来,白白浪费IO,关掉交换之后,所有数据都在内存里,自然更流畅。只有当物理内存真的不够的时候,交换才有用,内存够的时候,交换反而是累赘。

交换设计的本质:容量与性能的取舍

内存交换从诞生到现在,核心的设计逻辑一直没有变:它是一种权衡,用低速的存储换更多的内存容量,牺牲一部分性能换系统的可用性。

早期物理内存非常贵,容量很小,几十MB内存就要几百块,所以交换非常重要,没有交换系统根本跑不了几个程序;现在物理内存越来越便宜,容量越来越大,交换的作用慢慢变弱,很多服务器甚至直接关闭交换,因为性能比多撑一点容量更重要。

不管是传统的磁盘交换,还是现在的zram压缩交换,本质都是权衡:根据你手里的硬件资源,在容量、性能、成本之间找一个平衡点——如果有足够的物理内存,交换就是备用的应急方案,不需要的时候尽量不用;如果硬件资源有限,交换就是扩展容量的好工具,哪怕性能降一点,也比直接不能用好。

结语

内存交换不是什么高深的黑科技,它就是操作系统应对物理内存不足的一个经典解决方案:把不用的数据挪到低速存储,腾出空间给要用的数据,对应用透明,用空间换容量,解决了物理内存不够用的问题。

理解内存交换,我们就能明白很多系统问题的根源:为什么内存占满之后系统会卡死,为什么数据库要关交换,为什么zram适合移动设备,这些问题的答案都在交换的设计逻辑里。而交换背后“权衡利弊,按需取舍”的设计思路,也值得我们学习:没有绝对好的技术,只有适合场景的设计,用对了地方,就能发挥最大的价值。

现在物理内存越来越大,但内存交换依然没有被淘汰,它依然是Linux内存管理的重要部分,在不同场景下发挥着作用,搞懂它的原理,我们就能更好地配置系统,排查问题,让我们的服务运行得更稳定流畅。

本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除( 邮箱:macysun@21ic.com )。
换一批
延伸阅读

在现代计算机系统中,物理内存的稀缺性与程序对内存的无限需求始终存在矛盾。Linux操作系统通过虚拟内存技术,为每个进程构建了独立的内存抽象层,不仅解决了物理内存不足的问题,还实现了进程间的内存隔离与资源高效利用。

关键字: 虚拟内存 内存

在高并发场景下,Linux系统的并发处理能力直接决定了应用的性能和稳定性。无论是Web服务器、数据库还是消息队列,都需要面对大量并发连接的挑战。那么,Linux系统的最大并发数究竟是多少?它受到哪些因素的限制?又该如何优...

关键字: Linux系统 并发数

在嵌入式系统中,RAM(随机存取存储器)是支撑系统运行的核心硬件之一。与通用计算机动辄数GB的内存不同,嵌入式RAM通常以KB或MB为单位,其性能、功耗和成本直接决定了系统的整体表现。从早期的8位单片机到如今的32位、6...

关键字: SRAM 内存

在Linux系统中,函数调用是程序执行的基本操作之一。从用户态到内核态,从简单的函数调用到复杂的系统调用,背后都隐藏着一套严谨的机制。理解Linux函数调用的过程,不仅能帮助我们更好地编写高效的代码,还能深入理解操作系统...

关键字: Linux系统 函数

在计算机系统中,内存是程序运行的核心载体,但物理内存的容量始终有限。当多个程序同时运行导致物理内存耗尽时,操作系统如何保证系统的稳定运行?答案就是内存交换机制。作为操作系统的“内存调剂师”,内存交换机制通过将部分内存数据...

关键字: 内存 虚拟内存

在现代操作系统中,内存是影响系统性能的核心资源之一。当物理内存无法满足程序运行需求时,内存交换机制作为一种关键的内存管理策略,能够通过在物理内存与外部存储之间交换数据,为系统提供“虚拟”的内存扩展能力。从早期的磁盘交换到...

关键字: 内存 内存交换

在高并发、低延迟的现代软件系统中,内存管理的效率直接决定了系统的整体性能。传统的动态内存分配方式(如C++中的new/delete、C语言中的malloc/free)虽然使用便捷,但在频繁分配和释放内存的场景下,会产生严...

关键字: C语言 内存

在多核处理器普及的今天,并发编程已经成为提升系统性能的关键手段。然而,并发场景下的数据一致性、可见性和有序性问题,却常常让开发者陷入困境。内存模型与内存序作为并发编程的底层规则,决定了多线程环境下数据的读写行为,是理解并...

关键字: 内存 内存模型

在多核处理器成为标配的当下,并发编程成为开发者充分利用硬件性能、构建高效应用的必备技能。然而,并发场景下的线程安全问题却常常让开发者陷入困境,数据不一致、竞态条件等问题屡见不鲜。追根溯源,这些问题大多与内存模型密切相关。...

关键字: 内存 处理器

内存报警通常来得很晚,因为系统在崩之前往往还能正常跑上很久。嵌入式软件一旦把堆碎片和瞬时峰值都交给运行时去碰运气,故障就会表现成难复现的申请失败、任务异常复位,甚至某次版本升级后才冒出来的随机死机。

关键字: 嵌入式 内存 压峰值
关闭