一文详解物理内存不足时,Linux如何保住系统不崩溃
扫描二维码
随时随地手机看文章
无论是个人电脑还是服务器,物理内存的容量总是有限的——当运行的程序越来越多,物理内存被占满之后,操作系统该怎么办?直接拒绝新的内存请求?还是杀掉老进程?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内存管理的重要部分,在不同场景下发挥着作用,搞懂它的原理,我们就能更好地配置系统,排查问题,让我们的服务运行得更稳定流畅。





