内存分配[一] - 空闲链表和内存池
Posted 术道经纬
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了内存分配[一] - 空闲链表和内存池相关的知识,希望对你有一定的参考价值。
一个内存分配方法的好坏,一是体现在
分配的速度
上,其重要性在内存容量较大时会显得尤其明显,二是体现在对内存这种有限资源的
使用率
上。如果空闲内存被分割成了许多无法利用的小内存块(也就是俗称的内存碎片),那么即便系统中空闲内存的总量还很多,也可能无法满足程序内存分配的需求。
本系列文章将由浅入深,介绍内存分配常见的几种实现方式及其各自的应用场景。
将内存中所有的空闲内存块通过链表的形式组织起来,就形成了最基础的free list。内存分配时,扫描free list的各个空闲内存块,从中找到一个大小满足要求的内存块,并将该内存块从free list中移除。内存释放时,释放的内存块被重新插入到free list中。
灰色部分代表已经被分配的内存块,白色部分代表空闲的内存块,大小分别是48字节,16字节和96字节。
如果此时内存分配的申请是
12个字节
,那么将有几下三种策略可以选择:
-
First fit
(最先适配),就是从free list头部开始扫描,直到遇到第一个满足大小的空闲内存块,这里第一个48字节的内存块就可以满足要求。这种方法的优点是相对快一些,尤其是满足要求的空闲内存块位于链表前部的时候,但是在控制碎片数量上不是最优的。
-
Best fit
(最佳适配),就是遍历free list的所有空闲内存块,从中找到和所申请的内存大小最接近的空闲内存块,这里第二个16字节的内存块是最接近12字节的。
-
Worst fit
(最差适配)
,
也是遍历free list的所有空闲内存块,如果找不到大小刚好匹配的,就从最大的空闲内存块中分配。初看起来很反直觉是不是?但假设接下来的内存申请是64个字节,那只有worst fit的这种方法才能满足需求,所以其价值体现在:分配之后剩下的空闲内存块很可能仍然足够大。
以上讨论的是内存分配的情况,接下来看看内存释放的操作是怎样的。
假设从第80字节到第100字节中间的20字节内存被释放了,那么它将和前面相邻的空闲内存块合并:
接下来从第100字节到第112字节中间的12字节内存也被释放了,那么它将同时和前后相邻的空闲内存块合并:
不过,既然用到了链表,那就需要指针,而指针本身也是要占用内存空间的。而且在内存释放时,要判断被释放的内存块前后的内存块是不是也是空闲的,这就需要每个内存块有一个空闲状态的标志位。
可以采用的一种方式是bitmap,假设以4个字节为最小分配单位,那么每4字节需要一个bit,因此额外消耗的内存为1/32。
空闲链表的分配方式简单,但分配效率不高,运行一段时间后容易产生大量的内存碎片,从而恶化了内存利用率。
如果能将一大块内存分成多个小内存(称为内存池),不同的内存池又按照不同的「尺寸」分成大小相同的内存块(比如分别按照32, 64, 128……字节),同一内存池中的空闲内存块按照free list的方式连接。
每次分配的时候,选择和申请的的内存在「尺寸」上最接近的内存池,比如申请60字节的内存,就直接从单个内存块大小为64字节的内存池的free list上分配。
这样减少了free list链表的长度,能够缩短每次内存分配所需的线性搜索的时间,特别适合对实时性要求比较高的系统(比如RTOS)。
但要想取得好的效果,需要结合系统实际的内存分配需求,对内存池的大小进行合理的划分。比如一个系统常用的是256字节以下的内存申请,那设置过多的256字节以上的内存池,就会造成内存资源的闲置和浪费。
似乎不是很好把控到底怎样划分内存池最为合适?
下文
将要介绍的Linux中的buddy分配系统,将基于普通的内存池进行优化,以更贴合大型操作系统对内存管理的需求。
以上是关于内存分配[一] - 空闲链表和内存池的主要内容,如果未能解决你的问题,请参考以下文章
RT-Thread快速入门-内存池
c 链表和动态内存分配
顺序表和链表的比较
最先适应算法
JVM内存分配
实现池内存API-处理联合