内存管理
虚拟内存
虚拟地址
为了防止程序直接操作硬件真实地址,系统引出了两种地址,虚拟内存地址和物理内存地址,此外,操作系统还提供了一个机制,
将不同进程的虚拟地址和不同内存物理地址映射起来。
操作系统如何管理这两种地址之间的关系
- 内存分段:不同段有不同属性,如数据段,代码段。分段机制下,虚拟地址由段选择因子和段内偏移量组成
此外程序里的虚拟地址被分为4个段:
但分段所带来的问题是会 1.产生内存碎片,因为地址不连续,比如剩下100MB的内存可能是多块组成的,这样空间无法打开一个100MB的程序,
解决办法是内存交换,即回收内存时紧跟着已被占用的内存后面。2.导致内存交换效率低,内存交换如果是一个空间很大的程序,则会导致卡顿。 - 内存分页:为了解决内存分段导致的效率低问题,引出了内存分页,内存分页就是指把整个虚拟和物理内存空间切成一段段固定尺寸大小。这样保证了
剩余空间是连续的,不会有外部碎片产生,但是会存在内部碎片的产生,因为分配的程序会有不足一页的情况,内存分页下,虚拟地址与物理地址是通过
页表来映射,覆盖了全部的物理地址,当出现虚拟地址在页表中查不到时,会报错缺页异常,然后重新分配物理内存等,当出现内存不够时,系统会将最
近未使用的的内存页面写入到硬盘上(换出),等需要时再加载进来(换入),这种操作叫swap机制,因此一次性写入磁盘的页数不多,所以内存交换效率相对较高。
总的来说,只有在程序运行中,需要对应数据,才会加载到物理内存中。但进程越多,越需要更大的内存存储页表。 - 多级页表:为了解决内存分页页表过大的问题,此时虚拟地址组成是,一级页表,二级页表,页内偏移量,同样当没有使用某个一级页表项,就不会产生
二级页表这样既覆盖了所有物理地址,又能保证页表内存较小,由此可推,64位系统,变成了四级目录 - TLB:CPU中专门用于页表缓存等的Cache,通常将最常用的页表项存储在这里。
- 段页式内存管理:分段后再分页,虚拟地址由段号,段内页号,页内位移组成。而在代码段下设置一块不可访问区域可以防止代码跑飞了。
所以虚拟地址的作用是:
- 虚拟地址可以使进程运行内存超过它的物理内存。
- 可以保证独立,每个进程是有独立的页表,进程之间无法相互访问。
- 页表项中除物理地址外还有一些标记属性,能更好得提供安全性。
malloc如何分配内存
Linux的用户空间分布:
malloc如何分配内存:
malloc是C库里的函数,用于动态分配内存,且分配的是虚拟内存(被访问时才映射物理内存,且映射关系重新建立):
- 方式一:brk()系统调用从堆分配内存
分配方式是在堆顶申请内存,申请内存小于128KB时使用,且用free函数释放时,不归还内存给操作系统,而是存放在内存池中。 - 方式二:mmap()系统调用在文件映射区域分配内存
分配方式是在文件映射区域内分配,申请内存大于128kb时使用,free函数释放后,归还内存给操作系统。
malloc在分配内存空间时,会需分配更大的空间作为内存池,并且会多出16字节的位置(用于存放内存块信息)
内存满了之后的处理
内存回收
内存回收的方式主要有两种:
- 后台内存回收:唤醒Kswapd内核线程回收内存,异步进行,不阻塞进程的执行。
- 直接内存回收:后台内存回收跟不上时执行,同步进行,会阻塞进程的执行。
- OOM机制:当直接内存回收还是不能够满足物理内存的申请时执行,直接杀死占用物理内存较高的进程。(不算内存回收了此时)
主要被回收的内存有两类(回收基于LRU(最近最少使用)算法):
- 文件页:内核缓存的磁盘数据与文件数据都叫文件页,可以直接释放,那些被修改过但没存入磁盘的数据叫做脏页,回收它时是先写回磁盘再释放,这一步影响性能。
- 匿名页:没有实际载体,如堆,栈数据。回收此类数据是利用linux里的Swap机制,即将不常用的先写回磁盘中,然后释放,需要时读取即可,这一步影响性能。
过于频繁的进行内存回收会影响系统的性能,因此有以下解决方案:
- 调整文件页和匿名页的回收倾向:在Linux里有swappiness选项,范围为0-100,数值越大越积极使用swap,即越倾向回收文件页,因为文件页回收
对操作系统影响少一点。 - 尽早触发Kswapd内核线程异步回收内存,即尽早进行后台内存回收:触发条件是超过相关阈值,
内存压力大时,触发异步回收
NUMA架构下的内存回收策略
其中memory是存储设备,最早为smp架构,但因为多个CPU通过一个总线访问内存,总线带宽压力大,而NUMA架构将CPU分组,每组有着自己独立的资源
因此在NUMA架构下,系统可以回收自己组的内存,也可以回收其他组(Node)的内存,还可以从本地内存中回收内存。
如何保护一个进程不被OOM杀掉
Linux系统里有OOM_badness()函数,跟每个进程打分,分高的优先杀掉,得分受下面方向影响:
- 进程使用的物理内存页面数
- oom校准值,可以自行配置,范围为-1000~1000
因此对于一些重要的进程,可以调整校准值,甚至可以调成-1000,这样无论如何都不能被杀掉。
在4GB物理内存上申请8GB内存会咋样
32位系统场景
会显示申请失败,因为32位系统虚拟内存只有3GB大小。
64位系统场景
可以申请,因为虚拟内存足够,只有在程序运行时才会映射到物理内存。当然前提是开启了SWAP机制。
Swap机制:是一块磁盘空间或者本地文件,包含换出换入两个过程,一般在内存不足或者内存闲置时触发。
回到问题中,当没有开启Swap机制时,申请会发生OOM。
预读失效和缓存污染
Linux操作系统和MySQL的缓存
Linux读取文件时会缓存文件到Page Cache 中,如下图的页缓存:
Mysql缓存是设计了一个缓冲池(buffer pool):
预读失效
是指操作系统在读磁盘时会额外读取一些到内存中,但并未被使用。
这样做会导致不会被访问的数据占用了前排位置。
如何避免
将LRU链表进行改进:
- Linux改进方式:实现两链表,活跃链表和非活跃链表,此后预读页只需加入到非活跃链表中,需要时再加载到活跃链表头部
- MySOL的改进方式:划分young区域和old区域,预读页先加载到old区域头部,当真正访问时才插入到young的头部。
缓存污染
当读取大量数据时,会将大量热点数据淘汰出去,此时就是缓存污染。
如何避免
提高进入到活跃LRU链表的门槛:
- Linux系统:在内存页被访问第二次时,才放入。
- MySQL : 在内存页被访问第二次时,如果前后访问时间间隔在1秒内(默认值),就不放入,反之则放入到young区里。