内存池
上篇文章的阅读量目前为止竟然高达4000+,让作者真是受宠若惊啊(呃呃呃。毕竟是一只菜鸟啊!)。
正文开始咯:
内存池
上篇文章我们对于对对象构造前的内存配置和对象析构后的空间释放进行深入探索。详细介绍了空间配置器的,第一级空间配置器(malloc_alloc_template),第二级空间配置器(defalult_alloc_template).这篇文章将针对free list(第二级空间配置器的主要结构,读者可以去上篇文章详细了解)内存不足情况进行分析,其实就是如何处理分配空间不足的各种情况。
身为一个空间配置器(第二级),default_alloc_template拥有配置器的标准接口函数allocate(),此函数首先判断块大小,大于128bytes就调用第一级空间配置器,小于128bytes就检查对应的free list。如果free list之内有可用的块,就直接拿来使用,如果没有,就将区块的大小上调至8的倍数边界,然后调用refill()函数,准备为free list重新填充空间。
下面我们详细介绍refill函数如何填充空间,如果free list中没有可用的块时,就调用refill函数,准备为free list填充空间,新的空间来自内存池,由chunk_alloc()函数完成,缺省取得20个新节点(新区块),但万一内存池空间不够,获得的区块数可能小于20;
下面我们详细看看《STL源码剖析》中chunk_alloc()函数的源码:
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 = _S_end_free - _S_start_free; // 如果剩余大小满足要求,那么直接操作对应的指针即可 if (__bytes_left >= __total_bytes) { __result = _S_start_free; _S_start_free += __total_bytes; return(__result); } // 剩余大小不够,但是至少还能分配一个节点 else if (__bytes_left >= __size) { // 能够分配的节点数 __nobjs = (int)(__bytes_left/__size); __total_bytes = __size * __nobjs; __result = _S_start_free; _S_start_free += __total_bytes; return(__result); } // 一个节点的空间都不够了 else { // 新申请的空间为2倍大小+附加量 size_t __bytes_to_get = 2 * __total_bytes + round_up(_S_heap_size >> 4); // Try to make use of the left-over piece. // 如果还有剩余的空间,加入对应的free-list节点的链表 if (__bytes_left > 0) { _Obj* __STL_VOLATILE* __my_free_list = _S_free_list + _S_freelist_index(__bytes_left); ((_Obj*)_S_start_free) -> _M_free_list_link = *__my_free_list; *__my_free_list = (_Obj*)_S_start_free; } // 分配新的空间 _S_start_free = (char*)malloc(__bytes_to_get); // 如果操作失败 if (0 == _S_start_free) { size_t __i; _Obj* __STL_VOLATILE* __my_free_list; _Obj* __p; // Try to make do with what we have. That can't // hurt. We do not try smaller requests, since that tends // to result in disaster on multi-process machines. // 看看free-list数组中,是否有更大尺寸的可用节点 for (__i = __size;_i <= (size_t) _MAX_BYTES;__i += (size_t) _ALIGN) { __my_free_list = _S_free_list + _S_freelist_index(__i); __p = *__my_free_list; // 如果有可用节点则摘一个下来给内存池使用 if (0 != __p) { *__my_free_list = __p -> _M_free_list_link; _S_start_free = (char*)__p; _S_end_free = _S_start_free + __i; // 递归调用自身,修正__nobjs return(_S_chunk_alloc(__size, __nobjs)); // Any leftover piece will eventually make it to the // right free list. } } // 如果没有可用节点,则只能指望第一级空间配置器了 _S_end_free = 0; // In case of exception. _S_start_free = (char*)malloc_alloc::allocate(__bytes_to_get); // This should either throw an // exception or remedy the situation. Thus we assume it // succeeded. } _S_heap_size += __bytes_to_get; _S_end_free = _S_start_free + __bytes_to_get; // 递归调用自身,修正__nobjs return(_S_chunk_alloc(__size, __nobjs)); } }上述的chunk_alloc()函数以end_free-start_free来判断内存池容量的大小,如果容量充足,则直接调出20个区块返回给free list。如果容量不足以提供20个去开,但足以供一个以上的区块,就拔出这20个区块空间出去,这时候通过引用传递的nobjs参数将被修改为可返回的实际块数,如果内存池连一个块空间都无法分配,此时便利用malloc函数从堆区heap配置内存,为内存池增加容量以供需求,新的容量为需求量的二倍加上一个附加量(随着配置次数的增多越来越大的附加量)。万一山穷水尽,heap区也无法给内存池分配内存,malloc失败,chunk_alloc就去free list寻找有无“尚有围未用区块,且区块足够大”,找到就交出来,找不到就调用第一级的空间配置器malloc_alloc_template.其实也就是malloc来配置内存,相比于_default_alloc_template,他有out_of_memory机制(第一篇文章有详细介绍),或许有机会是释放其他的内存拿来使用,若果失败,就发出bad_alloc异常,到此为止。
针对分配的情况《STL源码剖析》有一个例子,读者可以去看看,有助于深入理解其分配机制。
总结一下:
chunk_alloc的流程总结如下:
1. 内存池有足够大小的空间,则分配申请的空间;
2. 内存池没有足够大小的空间,但是至少还能分配一个节点的空间,则能分多少分多少;
3. 内存池一个节点都腾不出来了,向系统的heap申请2倍于要求大小的空间,在此之间,如果内存池剩余有空间,则放到free-list中去;
4. 如果向heap申请空间失败,那么只能看free-list中更大的节点是否有可用空间了,有则用之,同时递归调用自身修正__nobjs;
5. 如果free-list也没有可用节点了,那么转向第一级空间配置器申请空间;
6. 再不行,第一级空间配置器就要抛出bad_alloc异常了;
注意如果有需求的话,内存池中会不断的通过malloc申请新的内存,最后内存池所拥有的内存也会越来越大,当然最后进程结束的时候,这些内存都会由操作系统收回。