我们在编写程序过程中,需要内存时,我们第一反应就是malloc,但是这样容易产生内片无法被利用。
在STL中提到了空间适配器,它主要分为两级:一级空间适配置器,二级空间配置器。一级空间适配器是对malloc的简单包装,它内部的allocate()和reallocate()都是在调用malloc()和realloc()不成功后,再调用oom_malloc()和oom_realloc()【清理内存】,重复多次后,如果还不成功便调用_THROW_BAD_ALLOC,丢出bad_alloc异常信息。
二级空间配置器:
二级空间配置器对内存的管理减少了小二区块造成的内存碎片,它主要是:如果所要申请的空间大于128字节,则直接交至以及空间配置器处理,如果小于128字节,则使用二级空间配置器,它是用一个16个元素的自由链表来管理的,每个位置下挂载着大小(分别为8、16、24、32、48、56、64、72、80、88、96、104、112、120、128字节),每次将所需内存提升到8的倍数。
free_list它的节点结构为一个共用体:
union obj
{
union obj * free_list_link;
char client_data[1];
};
二级空间配置器结构入下图所示:
自由链表中分出区块:
这样在完成工作的同时更加节省空间。在空间没有分配出去之前,该空间中一直存放的是所指向的下一个节点的地址,当该空间分配出去时,让对应的my_free_list指向它的下一个节点,然后将他内部的值改为要放入用户的data值,
它的内存分配主要分为以下四种情况:
1、所需内存下连接的区块和内存池其中一个为空
(1)、当内存池为空,但是所需空间大小提升为8的倍数后(入需要13bytes空间,我们会给它分配16bytes大小)所对应的free_list不为空时,它会像上图所示那样直接从对应的free_list中拔出。
if(n > __MAX_BYTES) //所需内存是否大于128bytes,如果大于128bytes则调用以及空间配置器。
{
return malloc_alloc::allocate(n);
}
//如果小于128bytes,调用二级空间配置器并找出16个free_list中时当的一个
my_free_list = free_list + FREELIST_INDEX(n);
result = *my_free_list;
//如果free_list中为空则准备填充(填充函数就如下面几种情况,下面会详细解释)
if(result == 0
{
void *r = refill(ROUND_UP(n));
return r;
}
//相应free_list有自由区块则调整free_list,拔除一个节点分配出去。
*my_free_list = result->free_list_link;
return result;
(2)、当对应的free_list为空,但是内存池不为空时:
(a)先检验它剩余空间是否够20个节点大小(即 所需内存大小(提升后) * 20),则直接从内存池中拿出20个节点大小空间,将其中一个分配给用户使用,另外19个当作自由链表中的区块挂在相应的free_list下。
//在调用时shunk_alloc()函数中参数nobjs值为20.
template<bool threads, int inst>
char* __default_alloc_template<threads, inst>::chunk_alloc(size_t size, int &nobjs)
{
char *result;
size_t total_bytes = size * nobjs; //内存池中分配的总内存大小
size_t bytes_left = end_free - start_free; //内存池中剩余空间大小
//判断内存池中剩余空间是否够需要的总空间
if(bytes_left >= total_bytes)
{
//如果够的话,则改变内存池的起始指针指向,让它指向此时内存池的首地址,并将分配出去的空间首地址(即内存池原来起始地址)返回
result = start_free;
start_free += total_bytes;
return result;
}
将分配的首地址返回后,拿出一个节点分配给用户使用,剩余的19个节点挂在相应的free_list中
(b)如果不够20个节点大小,则看它是否能满足1个节点大小,如果够的话则直接拿出一个分配给用户,然后从剩余的空间中分配尽可能多的节点挂在相应的free_list中
else if(bytes_left >= size) //判断内存池中剩余内存大小够不够一个节点空间
{
nobjs = bytes_left / size; //计算内存池中剩余空间最多可以分配出多少个节点
total_bytes = size * nobjs; //计算出内存池一共需要分配出多大内存
result = start_free; //记录分配出去空间的首地址
start_free += total_bytes; //改变内存池起始地址的指向
return result; //返回申请内存的首地址
}
(c)如果连一个节点内存都不能满足的话,则将内存池中剩余的空间挂在相应的free_list中【肯找到相应的free_list,因为每次给内存池申请空间都是申请8的倍数,分配时也是以8的倍数为单位,所以内存池中此时剩余的内存也肯定是8的倍数】,然后载给内存池申请内存(第二种情况)。
2、内存池为空,所需要空间大小下连接的区块也为空
此时二级空间配置器会使用malloc()从heap上申请内存,但它一次所申请的内存大小为2倍的20个所要的内存大小(即 2 * 所需节点内存大小(提升后)* 20),申请成功后,将其中一半(即20节点空间)内存放入内存池中,然后从剩下的一半中拿出一个节点空间大小分配给用户,剩下的19个空间节点挂在载相应的free_list下。【之所以二级空间配置器每次申请40倍所需节点大小空间,是为了避免不停从heap上申请内存的麻烦,但然,之所以选择40倍的节点大小,这是编译者的一个经验值。】
3、malloc没有成功
在第二种情况下,如果malloc()失败了,说明heap上没有足够空间分配给我们了,这是,二级空间配置器会从比所需节点空间大的free_list中一一搜索,从任意一个比它所需节点空间大的free_list中拔除一个节点来使用。
4、查找失败,调用一级空间配置器
第三种情况下,如果查找失败了,说明比他大的free_list中都没有自由区块了,此时会调动一级空间配置器oom_allocate(),它的原理手机清理内存差不多,它是将系统中能够回收的内存回收,然后为二级空间适配器所使用。
如果oom_allocate()也失败了,那说明内存已经山穷水尽了,这是只能返回“out_of_memory!”咯。
//如果内存池中剩余的空间连一个结点都不够,则先将内存池中剩余空间挂在在相应free_list下以防这块内存丢失,然后从heap上申请40倍节点空间
size_t bytes_to_get = 2 * total_bytes + ROUND_UP(heap_size >> 4);
if(bytes_left > 0) //判断内存池中是否有空间剩余
{
//如果有,找出剩余内存所对应free_list中,将其挂在它下面,
obj * volatile * my_free_list = free_list + FREELIST_INDEX(bytes_left);
((obj*)start_free)->free_list_link = *my_free_list;
*my_free_list = (obj*)start_free;
}
//让内存池的起始指针指向心申请的空间的首地址
start_free = (char *)malloc(bytes_to_get);
if(0 == start_free) //判断是否申请失败
{
//情况3:malloc()失败
//如果申请失败
int i;
obj * volatile * my_free_list, *p;
//从大于size开始的free_list中一一查找,看是否还有没有分配出去的区块
for(i = size; i<=__MAX_BYTES; i+=__ALIGN)
{
my_free_list = free_list + FREELIST_INDEX(i);
p = *my_free_list;
if(0 != p)
{ //找到后,拔除一个区块来使用,从中拿出size大小分配给用户,剩余的当作内存池空间
*my_free_list = p->free_list_link;
start_free = (char *)p;
end_free = start_free + i;
return chunk_alloc(size,nobjs);
}
}
第4种情况:查找失败:
//调用一级空间配置器
end_free = 0;
start_free =(char*)malloc_alloc::allocate(bytes_to_get);
} //如果时内存不足的清况有所改善皆大欢喜,如果不能成功则抛出异常“out_of_memory!”
heap_size += bytes_to_get;
end_free = start_free + bytes_to_get;
//递归调用自己,尽可能的改善这一情况
return chunk_alloc(size,nobjs);
空间回收问题:
当用户从二级空间配置器中申请的内存被释放时,二级空间配置器要将其回收然后插入对应的free_list,其过程如下图所示:
static void deallocate(void *p,size_t n)
{
obj *q = (obj *)p;
obj * volatile * my_free_list;
//判断所释放内存是否大于128bytes
if(n > __MAX_BYTES)
{
//如果大于128bytes,则用一级空间配置器回收
malloc_alloc::deallocate(p,n);
return;
}
//如果小于128bytes,则使用二级空间配置器回收。
my_free_list = free_list + FREELIST_INDEX(n);
q->free_list_link = *my_free_list;
*my_free_list = q;
}
这就是二级空间配置器的示现过程,以及申请过程中各种情况的申请的解决放法,因为主要是总结思想,所以代码比较零散,如果需要完整代码,可以查看sgi-STL源码库.
源码下载地址:http://www.sgi.com/tech/stl/download.html