在学习第七章7.8节时关于存储器分配这一块的时候,有个疑问,malloc(当然包括calloc和realloc)申请内存时候是怎么样申请的,free的时候内存是直接还给操作系统么?这个也是我在阿里面试时面试官所问的一道基础题。以下是摘自网友的解释,很详细。
要解释这个问题首先要了解Linux进程使用内存的基本流程,进程的堆并不是直接建立在Linux的内核的内存分配策略上的,而是建立在glibc的堆管理策略上的(也就是glibc的动态内存分配策略上),堆的管理是由glibc进行的。 所以我们调用free对malloc得到的内存进行释放的时候,并不是直接释放给操作系统,而是还给了glibc的堆管理实体,而glibc会在把实际的物理内存归还给系统的策略上做一些优化,以便优化用户任务的动态内存分配过程。
glibc维护了不止一个不定长的内存块链表,而是好几个,每一个这种链表负责一个大小范围,这种做法有效减少了分配大内存时的遍历开销,类似于哈希的方式,将很大的范围的数据散列到有限的几个小的范围内而不是所有数据都放在一起,虽然最终还是要在小的范围内查找,但是最起码省去了很多的开销,如果只有一个不定长链表那么就要全部遍历,如果分成3个,就省去了2/3的开销,总之这个策略十分类似于散列。glibc另外的策略就是不止维护一类空闲链表,而是另外再维护一个缓冲链表和一个高速缓冲链表,在分配的时候首先在高速缓存中查找,失败之后再在空闲链表查找,如果找到的内存块比较大,那么将切割之后的剩余内存块插入到缓存链表,如果空闲链表查找失败那么就往缓存链表中查找,这么查找有什么依据吗?实际上是有的,正是这个方式让glibc有了自己的策略。
在free的时候如果能合并在堆顶,也就是能和堆顶的空闲元素合并,那么就合并,因为堆的缩减仅仅在堆顶的空闲元素达到一定量的时候才会进行,因此为了尽快将内存归还操作系统,尽量优先考虑堆顶的释放,但是如果不能合并,比如它和堆顶根本就没有相邻,那么如果该释放的块大小小于80字节,那么就直接将之挂在高速缓存中,为了防止别的块和它合并所以并不更改使用位,这里可以看到,glibc实际上为小于80字节的小内存块维护了一个高速的内存池,如果有小块内存需求,直接从此池中拿走一个即可,只需要从高速缓存摘除之并不需要修改使用位,因为高速缓存中的元素的使用位均为1,这个高速缓存在有大内存块分配需求并且几个分配策略都失败的时候会被回收,回收进空闲链表的过程涉及到相邻块的合并,合并之后就有可能满足稍微大一些的内存分配需求,这里为何将界限定位为80个字节呢?实际上是一个经验值,那么介于80字节和128k字节之间的内存块在释放的时候要将使用位设置为0,然后试图和相邻块合并,然后挂入缓存链表。
原文: