只有在访问已分配的虚拟地址空间的时候,操作系统通过查找页表,发现虚拟内存对应的页没有在物理内存中,就会触发缺页中断,然后操作系统会建立虚拟内存和物理内存之间的映射关系。
(相关资料图)
前面我们我们已经了解了linux是如何进行内存分配的、虚拟内存和物理内存的关系、虚拟内存如何管理,今天我们来学习一下系统内存满了,会发生什么?以及会带来什么问题?大致分成这四个内容来进行学习。
内存分配的过程哪些内存可以被回收内存回收带来的问题如何保障一个进程不被kill前面我们已经学习过应用程序通过 malloc 函数申请内存的,需要注意的是,malloc() 分配的是虚拟内存。
如果分配后的虚拟内存没有被访问的话,虚拟内存是不会映射到物理内存的,这样就不会占用物理内存了。
只有在访问已分配的虚拟地址空间的时候,操作系统通过查找页表,发现虚拟内存对应的页没有在物理内存中,就会触发缺页中断,然后操作系统会建立虚拟内存和物理内存之间的映射关系。
缺页中断就是要访问的页不在主存,需要操作系统将其调入主存后再进行访问。在这个时候,被内存映射的文件实际上成了一个分页交换文件。
如果没有空闲的物理内存,那么内核就会开始进行回收内存的工作,回收的方式主要是两种:直接内存回收和后台内存回收。
后台内存回收(kswapd):在物理内存紧张的时候,会唤醒 kswapd 内核线程来回收内存,这个回收内存的过程异步的,不会阻塞进程的执行。直接内存回收(direct reclaim):如果后台异步回收跟不上进程内存申请的速度,就会开始直接回收,这个回收内存的过程是同步的,会阻塞进程的执行。如果直接内存回收后,空闲的物理内存仍然无法满足此次物理内存的申请,那么内核就会放最后的大招了 ——触发 OOM 机制。
OOM Killer 机制会根据算法选择一个占用物理内存较高的进程,然后将其杀死,以便释放内存资源,如果物理内存依然不足,OOM Killer 会继续杀死占用物理内存较高的进程,直到释放足够的内存位置。
物理内存申请过程如下图:
系统内存紧张的时候,就会进行回收内存的工作,那具体哪些内存是可以被回收的呢?
主要有两类内存可以被回收,而且它们的回收方式也不同。
文件页(File-backed Page):内核缓存的磁盘数据(Buffer)和内核缓存的文件数据(Cache)都叫作文件页。大部分文件页,都可以直接释放内存,以后有需要时,再从磁盘重新读取就可以了。而那些被应用程序修改过,并且暂时还没写入磁盘的数据(也就是脏页),就得先写入磁盘,然后才能进行内存释放。所以,回收干净页的方式是直接释放内存,回收脏页的方式是先写回磁盘后再释放内存。匿名页(Anonymous Page):这部分内存没有实际载体,不像文件缓存有硬盘文件这样一个载体,比如堆、栈数据等。这部分内存很可能还要再次被访问,所以不能直接释放内存,它们回收的方式是通过 Linux 的 Swap 机制,Swap 会把不常访问的内存先写到磁盘中,然后释放这些内存,给其他更需要的进程使用。再次访问这些内存时,重新从磁盘读入内存。Swap分区在系统的物理内存不够用的时候,把硬盘内存中的一部分空间释放出来,以供当前运行的程序使用。那些被释放的空间可能来自一些很长时间没有什么操作的程序,这些被释放的空间被临时保存到Swap分区中,等到那些程序要运行时,再从Swap分区中恢复保存的数据到内存中。
文件页和匿名页的回收都是基于 LRU 算法,也就是优先回收不常访问的内存。LRU 回收算法,实际上维护着 active 和 inactive 两个双向链表,其中:
active_list活跃内存页链表,这里存放的是最近被访问过(活跃)的内存页;inactive_list不活跃内存页链表,这里存放的是很少被访问(非活跃)的内存页;越接近链表尾部,就表示内存页越不常访问。在回收内存时,系统就可以根据活跃程度,优先回收不活跃的内存。
回收内存方式的不同。回收的内存类型的不同会带来不同的影响,下面我们就来学习一下不同的是否,不同的类型会带来怎样的影响。回收内存方式:后台回收:直接回收
一种是后台内存回收,也就是唤醒 kswapd 内核线程,这种方式是异步回收的,不会阻塞进程。一种是直接内存回收,这种方式是同步回收的,会阻塞进程,这样就会造成很长时间的延迟,以及系统的CPU 利用率会升高,最终引起系统负荷飙高。可被回收的内存类型:文件页和匿名页
文件页的回收:对于干净页是直接释放内存,这个操作不会影响性能,而对于脏页会先写回到磁盘再释放内存,这个操作会发生磁盘 I/O的,这个操作是会影响系统性能的。匿名页的回收:如果开启了 Swap 机制,那么 Swap 机制会将不常访问的匿名页换出到磁盘中,下次访问时,再从磁盘换入到内存中,这个操作是会影响系统性能的。可以看到,回收内存的操作基本都会发生磁盘 I/O 的,如果回收内存的操作很频繁,意味着磁盘 I/O 次数会很多,整个系统给人的感觉就是很卡。
在系统空闲内存不足的情况,进程申请了一个很大的内存,如果直接内存回收都无法回收出足够大的空闲内存,那么就会触发 OOM 机制,内核就会根据算法选择一个进程杀掉。
Linux 到底是根据什么标准来选择被杀的进程呢?这就要提到一个在 Linux 内核里有一个oom_badness()函数,它会把系统中可以被杀掉的进程扫描一遍,并对每个进程打分,得分最高的进程就会被首先杀掉。
进程得分的结果受下面这两个方面影响:
第一,进程已经使用的物理内存页面数。第二,每个进程的 OOM 校准值 oom_score_adj,我们可以在设置 -1000 到 1000 之间的任意一个数值,调整进程被 OOM Kill 的几率。函数 oom_badness() 里的最终计算方法是这样的:
// points 代表打分的结果// process_pages 代表进程已经使用的物理内存页面数// oom_score_adj 代表 OOM 校准值// totalpages 代表系统总的可用页面数points = process_pages + oom_score_adj * totalpages / 1000
计算出来的值越大,那么这个进程被 OOM Kill 的几率也就越大。
每个进程的 oom_score_adj默认值都为 0,所以最终得分跟进程自身消耗的内存有关,消耗的内存越大越容易被杀掉。我们可以通过调整 oom_score_adj 的数值,来改成进程的得分结果:
如果你不想某个进程被首先杀掉,那你可以调整该进程的 oom_score_adj,从而改变这个进程的得分结果,降低该进程被 OOM 杀死的概率。如果你想某个进程无论如何都不能被杀掉,那你可以将 oom_score_adj 配置为 -1000。我们最好将一些很重要的系统服务的 oom_score_adj 配置为 -1000,比如 sshd,因为这些系统服务一旦被杀掉,我们就很难再登陆进系统了。
不建议将我们自己的业务程序的 oom_score_adj 设置为 -1000,因为业务程序一旦发生了内存泄漏,而它又不能被杀掉,这就会导致随着它的内存开销变大,OOM killer 不停地被唤醒,从而把其他进程一个个给杀掉。
推荐阅读
关于我们| 联系方式| 版权声明| 供稿服务| 友情链接
咕噜网 www.cngulu.com 版权所有,未经书面授权禁止使用
Copyright©2008-2023 By All Rights Reserved 皖ICP备2022009963号-10
联系我们: 39 60 29 14 2@qq.com