内存管理(二)
三种类型:
1 sequential fit(连续的)
通过链表将空闲内存链接起来,称为freelist。
分配内存时,先从freelist中查找是否存在满足要求的内存块,如果不存在,再从未分配内存中获取;当我们找到合适的内存块后,分配合适的内存,并将多余的部分放回freelist。
包括first fit,next fit,以及best fit
这种算法的实现基本来说都是基于一个双向链表或者循环链表来保存所有的free memory.而且一般都会使用kunth的boundary tag算法来合并临近的内存(这个算法可以去网上搜索下,这里就不介绍了).
其中free block的顺序一般为FIFO,LIFO或者address order(AO).
fist fit : 也就是从头开始搜索,找到第一个可以满足请求大小的block,当找到的block比请求的大的话,就分割这个block将剩余的插入到free list中.我们可以看到,这样的话会使得前面的block越来越小,从而导致每次搜索都会越来越远.
next fit: 它是从最后一次搜索停止的地方开始搜索,找到下一个满足请求大小的block.可以看做是优化版的 first fit.
best fit: 它是每次都是遍历list,然后找到满足请求大小的最小的那个block.它是从生成的内存碎片来看,最好的一种策略,因为它会产生最小的碎片.可是由于它会每次遍历所有block,所以它的效率比较低.为了解决它的碎片问题,那就是每次提交给请求者的内存都会大于等于它的请求值.不过这样会导致内存浪费. 而segreganted fit算法可以看做是best fit的一种很好的补充(下面会介绍这个算法).
空闲链表(free list)
将内存中所有的空闲内存块通过链表的形式组织起来,就形成了最基础的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。

2 segreganted free list

这种数据结构其实也就是将相同大小的block放在一个链表,然后将将这些链表再组合成一个链表或者数组(可以看到memcached也就是用的这种算法).当请求到来时,从最合适的size大小的链表中取得一个block.而一般block的大小都是2的次幂.比如2,4,8等等.
segreganted fit算法
也就是当请求到来时,会将每次提交的值按一定的规则对齐,然后从free list搜索的话就按对齐后的这个值来搜索,一般都是先从数组或者链表中去的当前的size class然后再在这个size class中搜索可用的block(memcached也就是使用的这种内存管理算法).
3 buddy system
包括binary buddy和double buddy.
buddy system可以说是segreganted free list的一个变体.它只不过提供了一个受限制的但是高效的分割和组合内存块的算法.在一个简单的buddy结构中,整个内存堆被分为两个块,这两个块就称作一对buddy.而当内存请求到来时,它会像segreganted free list中处理的那样,先将请求大小对齐,然后再递交给buddy system.将会不断地平均切割内存,直到得到一个最小的满足请求的大小的块.而当内存块被释放时,它会尝试合并内存块,而合并内存块的话,一个内存块只能和它的buddy合并.
可以看下面的图:
binary buddy
这个是最简单的也是最流行的,所有的buddy大小都是2的次幂,然后每次分割都是将一个块分为相等的两个块.这个缺点就是容易造成内存碎片.

Fibonacci buddy
和binary buddy 类似,只不过buddy大小是按Fibonacci排列(16, 32, 48, 80, 128, 208...)
double buddy
这个它会使用两个buddy system,比如一个是按binary buddy(2,4,8,16...)而另一个则使用2的倍数并且起始数是一个不同的值(3,6,12....).当请求到来,会选择一个最合适的块.这个会很大的降低内存碎片.
通过这个解决方法我们也可以在segreganted fit中使用,也就是我们可以提供两种机制,也就是每次提供一对内存块给请求,然后选择最合适的(可以试下改进memcached).
浙公网安备 33010602011771号