上一篇介绍完了虚拟内存,这篇将要给内存管理收个尾,介绍以下内存是如何回收的。
这里所要讲的内存回收,并不是虚拟内存中的页面置换(当可使用的物理空间不足时,需要把部分页换出),而是指对用户空间中的堆段和文件映射段进行回收(用户使用 malloc、mmap 等分配出去的空间),相当于操作系统层面的自动的 free()。
Memory Leak,内存泄漏是指向系统申请分配内存,可是使用完了以后却不归还,结果系统也不能再次将它分配给需要的程序。
内存回收指的是对用户空间中的堆段和文件映射段进行回收(用户使用 malloc、mmap 等分配出去的空间)。用户可以手动地使用 free() 等进行内存释放。当没有空闲的物理内存时,内核就会开始自动地进行回收内存工作。回收的方式主要是两种:后台内存回收和直接内存回收。
如果直接内存回收后,空闲的物理内存仍然无法满足此次物理内存的申请,那么内核就会触发 OOM (Out of Memory)机制,根据算法选择一个占用物理内存较高的进程,然后将其杀死,释放内存资源,直到释放足够的内存。

虽然在编程语言层面就已经提供了垃圾回收机制(Garbage Collection),但是当程序申请内存的速度远远大于内存回收的速度,还是会发生内存不足,所以还需要操作系统实现内存回收。
主要有两类内存可以被回收,而且它们的回收方式也不同。
文件页和匿名页的回收都是基于 LRU (最近最少使用)算法的。回收内存的操作基本都会发生磁盘 I/O,如果回收内存的操作很频繁,意味着磁盘 I/O 次数会很多,会影响系统的性能。
虽然编程语言中有垃圾回收机制(Garbage Collection),可以回收用户申请的内存空间,但是由于存在申请速度远大于回收速度、内存使用后未释放等情况,所以操作系统还需要实现内存回收机制来兜底。
回收的方式有后台内存回收、直接内存回收和 OOM。后台内存回收是异步的,不会阻塞进程,回收速度较慢;直接内存回收是同步的,会阻塞进程,回收速度快;如果回收的速度仍然赶不上,则会触发 OOM 机制,以占用内存从高到低依次杀死进程,直到内存足够使用为止。
知道了用户空间的堆段和文件映射段是如何被回收的,那他们是怎么被分配的呢?
在虚拟内存的用户空间分段中曾经讲过,堆段是用来存储动态分配的内存的,是由用户自行申请使用的。接下来以最常见的 C 语言中的 malloc() 为例进行说明。
malloc() 并不是系统调用,而是 C 库里的函数,用于动态分配内存。malloc 申请内存的时候,会有两种方式向操作系统申请堆内存。
方式一实现的方式很简单,就是通过 brk() 函数将「堆顶」指针向高地址移动,获得新的内存空间。

方式二通过 mmap() 系统调用中「私有匿名映射」的方式,在文件映射区分配一块内存,也就是从文件映射区“偷”了一块内存。

malloc() 分配的是虚拟内存,如果分配后的虚拟内存没有被访问的话,是不会将虚拟内存不会映射到物理内存,这样就不会占用物理内存了。只有在访问已分配的虚拟地址空间的时候,操作系统通过查找页表,发现虚拟内存对应的页没有在物理内存中,就会触发缺页中断,然后操作系统会建立虚拟内存和物理内存之间的映射关系。
free 释放:
malloc 返回给用户态的内存起始地址会比进程的堆空间起始地址多 16 字节,用于保存了该内存块的描述信息,比如有该内存块的大小。
这样当执行 free() 函数时,free 会对传入进来的内存地址向左偏移 16 字节,然后从这个 16 字节的分析出当前的内存块的大小,自然就知道要释放多大的内存了。

优点:
缺点: