深入理解堆:malloc和内存池

352 阅读3分钟

malloc 实现的基本原理是先向操作系统申请一块比较大的内存,然后再通过各种优化手段让内存分配的效率最大化。在 glibc 的实现里,malloc 函数在向操作系统申请堆内存时,会使用 mmap,以 4K 的整数倍一次申请多个页。这样的话,mmap 的区域就会以页对齐,页与页之间的排列非常整齐,避免了出现内存碎片。

对小块内存进行精细化管理,最常用的数据结构就是链表。为了能够方便地进行分配和回收,人们把空闲区域记录到链表里,这就是空闲链表 (free list)。

空闲链表里的节点主要是为了记录内存的开始位置和长度。

image.png

简单算法

当分配内存的请求到达以后,我们就通过遍历 free list 来查找可用的空闲内存区域,在找到合适的空闲区域以后,就将这一块区域从链表中摘下来。比如要请求的大小是 m,就将这个结点从链表中取下,把起始位置向后移动 m,大小也相应的减小 m。将修改后的结点重新挂到链表上。

在释放的时候,将这块区域按照起始起址的排序放回到链表里,并且检查它的前后是否有空闲区域,如果有就合并成一个更大的空闲区。

缺点:会产生内存碎片,分配效率一般,且多线程并发场景下性能还会恶化。

分桶式内存管理

分桶式内存管理采用了多个链表,对于单个链表,它内部的所有结点所对应的内存区域的大小是相同的。

最常见的方式是以 4 字节为最小单位,把所有 4 字节的区域挂到同一个链表上,再把 8 字节的区域挂到一起,然后是 16 字节,32 字节,这样以 2 次幂向上增长。如下图所示:

image.png

分配的时候,我们要只要找到能满足这一次分配请求的最小区域,然后去相应的链表里把整块区域都取下来。释放时,只需要把要释放的内存直接挂载到相应的链表里就可以了。

缺陷:区域内部的使用率不够高和动态扩展能力不够好。

伙伴系统

这种不断地把一块内存分割成更小的两块内存的做法,就是伙伴系统,这两块更小的内存就是伙伴。 它的好处是可以动态地根据分配请求将大的内存分割成小的内存。当释放内存时,如果系统发现与被释放的内存相邻的那个伙伴也是空闲的,就会把它们合并成一个更大的连续内存。通过这种拆分,系统就变得更加富有弹性。

总结

image.png


此文章为7月Day9学习笔记,内容来源于极客时间《编程高手必学的内存知识》