上节在学习第二级配置器时了解了第二级配置器通过内存池与自由链表来处理小区块内存的申请。但只是对其概念进行点到为止的认识,并未深入探究。这节就来学习一下自由链表的填充和内存池的内存分配机制。

refill()函数——重新填充自由链表

  前情提要,从上节第二级配置器的源码中可以看到,在空间配置函数allocate()中,当所需的某号自由链表为空时,才会调用refill()函数来填充链表。refill()函数默认申请20块区块的内存(5行),但所得内存不一定就是20块,要看当前内存池的剩余情况和堆容量的情况,这个在学习chunk_alloc()函数时会详解讨论,然后将第零块返回给allocate()函数,allocate()函数再返回给真正申请内存的用户,剩余内存分成区块串接成自由链表。

 template <bool threads, int inst>
void* __default_alloc_template<threads, inst>::refill(size_t n)
{
int nobjs = ; //需要填充自由链表时,尝试分配20个区块作为自由链表的新结点
char * chunk = chunk_alloc(n, nobjs); //交给chunk_alloc去分配内存,后述
obj * __VOLATILE * my_free_list;
obj * result;
obj * current_obj, * next_obj;
int i;
if ( == nobjs) return(chunk); //如果只分配到1个区块,则直接返回给申请者,自由链表无新结点
my_free_list = free_list + FREELIST_INDEX(n);
/* Build free list in chunk */
result = (obj *)chunk; //如果分配到大于1个区块,即将第0个区块返回给申请者,剩下的作为自由链表的空闲区块
*my_free_list = next_obj = (obj *)(chunk + n); //从新分配内存的第一个区块开始串接成自由链表
for (i = ; ; i++) {
current_obj = next_obj;
next_obj = (obj *)((char *)next_obj + n);
if (nobjs - == i) {
current_obj -> free_list_link = ;
break;
} else {
current_obj -> free_list_link = next_obj;
}
}
return(result);
}

chunk_alloc()函数——内存池

  其实内存池仅仅是靠三个静态成员变量来管理的,并没有多复杂的机制:

static char *start_free;    //内存池起始指针
static char *end_free; //内存池的尾指针
static size_t heap_size; //多次调用内存池, 就会在内存池中分配更多的内存, 这就是一个增量.

  这在上节的第二级配置器源码中也有提到。真正复杂的是为内存池的分配内存和自由链表与内存池的交互上,这两个在chunk_alloc()上有所体现。

 template <bool threads, int inst>
char*
__default_alloc_template<threads, inst>::chunk_alloc(size_t size, int&
nobjs) //注意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);
}
else if (bytes_left >= size) { //如果内存池没有足够的大小,但能够供应一个以上的区块,修正能够提供的区块个数
nobjs = bytes_left / size;
total_bytes = size * nobjs;
result = start_free;
start_free += total_bytes;
return(result);
}
else { //内存池连一个区块的大小都无法提供了,计算准备向堆申请的内存大小
size_t bytes_to_get = * total_bytes + ROUND_UP(heap_size >> );
// 如果当前内存池要有剩余,寻找哪号自由链表能收了它.
if (bytes_left > ) {
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 ( == start_free) { //堆空间也不足了,malloc()失败
int i;
obj * __VOLATILE * my_free_list, *p;
//尝试查看更大号的自由链表中是否有1个空闲块能供我们使用
for (i = size; i <= __MAX_BYTES; i += __ALIGN) {
my_free_list = free_list + FREELIST_INDEX(i);
p = *my_free_list;
if ( != p) { //如果有,把它从自由链表中划进内存池,并返回递归调用chunk_alloc()函数的结果,修正nobjs
*my_free_list = p->free_list_link;
start_free = (char *)p;
end_free = start_free + i;
return(chunk_alloc(size, nobjs)); //在递归调用chunk_alloc后,会发现内存池拥有了大于1个区块的内存了,可以把该区块返回给refill()函数
}
}
//堆空间没内存,其它自由链表也没内存,山穷水尽
end_free = ;
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));
}
}

  总结上述源码的功能,以end_free - start_free来判断内存池大小是否足够,如果充足则直接调出20个区块返回给自由链表。如果不足20个区块但还足够供应一个以上的区块,就拔出这不足20个区块的空间出去。这时pass by reference的nobjs将被修改为实际能够供应的区块数。如果内存池连一个区块空间都无法提供,此时便需利用malloc(),从堆中配置内存,为内存池注入足够的内存以应付需求,这足够的内存(22行)是指需求量的两倍再加上随着配置次数增加而越来越大的附加量(heap_size),如果能通过malloc()申请到40块内存,就将第前20块给自由链表,后20块放进内存池备用。

  举个实际的例子,让我们对这段源码有更深刻的认识:假设程序一开始,客端调用chunk_alloc(32, 20),于是malloc()配置40个32bytes区块,其中第一个交出,另外19个交给free_list[3]维护,剩余的20个留给内存池。接下来客端调用chunk_alloc(64, 20),此时free_list[7]空空如也,必须向内存池要求支持。内存池只够供应(32*60)/64=10个64bytes区块,就把这10个区块返回,第1个交给客端,余下交由free_list[7]维护。此时内存池空空如也。再调用chunk_alloc(96, 20),free_list[11]也空空如也,必须向内存池要求支持,而内存池表示爱莫能助,只能再向堆申请40+n个96bytes区块的内存,其中第1个交出,另外19个交给free_list[11]维护,余20+n个区块留给内存池......

  万一真的山穷水尽,整个堆空间都不够了,malloc()失败,chunk_alloc()就四处寻找有无“有未用且足够大区块”的自由链表。找到了就挖一块交出,找不到就调用第一级配置器。第一级配置器也使用malloc()来配置内存,但它有out-of-memory处理机制,或许有机会释放其它内存拿来此处使用。如果可以就成功,不可以就发出bad_alloc异常。

STL源码剖析——空间配置器Allocator#3 自由链表与内存池的更多相关文章

  1. STL源码剖析 — 空间配置器(allocator)

    前言 以STL的实现角度而言,第一个需要介绍的就是空间配置器,因为整个STL的操作对象都存放在容器之中. 你完全可以实现一个直接向硬件存取空间的allocator. 下面介绍的是SGI STL提供的配 ...

  2. STL源码剖析——空间配置器Allocator#2 一/二级空间配置器

    上节学习了内存配置后的对象构造行为和内存释放前的对象析构行为,在这一节来学习内存的配置与释放. C++的内存配置基本操作是::operator new(),而释放基本操作是::operator del ...

  3. STL源码剖析——空间配置器Allocator#1 构造与析构

    以STL的运用角度而言,空间配置器是最不需要介绍的东西,因为它扮演的是幕后的角色,隐藏在一切容器的背后默默工作.但以STL的实现角度而言,最应该首先介绍的就是空间配置器,因为这是这是容器展开一切运作的 ...

  4. STL源码剖析(空间配置器)

    前言 在STL中,容器的定义中都带一个模板参数,如vector template <class T, class Alloc = alloc> class vector {...} 其中第 ...

  5. STL源码剖析:配置器

    作用:对内存的管理 接口:申请和释放 内容: 几个全局函数 一级配置器 二级配置器 准备知识 POD是什么: Plain Old Data简称POD,表示传统的C语言类型:与POD类型对应的是非POD ...

  6. STL学习笔记:空间配置器allocator

    allocator必要接口: allocator::value_type allocator::pointer allocator::const_pointer allocator::referenc ...

  7. 《STL源码剖析》相关面试题总结

    原文链接:http://www.cnblogs.com/raichen/p/5817158.html 一.STL简介 STL提供六大组件,彼此可以组合套用: 容器容器就是各种数据结构,我就不多说,看看 ...

  8. 面试题总结(三)、《STL源码剖析》相关面试题总结

    声明:本文主要探讨与STL实现相关的面试题,主要参考侯捷的<STL源码剖析>,每一个知识点讨论力求简洁,便于记忆,但讨论深度有限,如要深入研究可点击参考链接,希望对正在找工作的同学有点帮助 ...

  9. STL源码剖析之空间配置器

    本文大致对STL中的空间配置器进行一个简单的讲解,由于只是一篇博客类型的文章,无法将源码表现到面面俱到,所以真正感兴趣的码农们可以从源码中或者<STL源码剖析>仔细了解一下. 1,为什么S ...

随机推荐

  1. 关于密码重用参数PASSWORD_REUSE_TIME,PASSWORD_REUSE_MAX之间的关系及其演示

    转自: https://blog.51cto.com/carefree/1382811 测试环境:10.2.0.2.0测试用户:SCOTT测试用的三组密码:oracle1 oracle2 oracle ...

  2. IDEA控制台乱码终极解决方案

    1. 问题描述 由于本机的IDEA 2019.1出现了无法连接插件商店和Spring Boot模板的问题,就重装了了最新的IDEA 2019.2.4版本,使用了一段时间以后,没有改任何的配置,控制台的 ...

  3. [转载]运行中的DLL自升级

      最近手头有个需求:dll需要注入到某个进程常驻,该dll具备自我升级能力,当发现新的可用版本时,立即Free自己,加载新的.下面是一个实现方案: 开启一个监听线程,从网络上拉新的可用版本,下载放到 ...

  4. win7+64位+Java学习基本软件安装+环境配置+eclipse(IDE)

    一.下载安装JDK 1.安装包下载地址:http://www.oracle.com/technetwork/java/javase/downloads/jdk9-downloads-3848520.h ...

  5. Spring Boot Actuator:健康检查、审计、统计和监控(转)

    Spring Boot Actuator可以帮助你监控和管理Spring Boot应用,比如健康检查.审计.统计和HTTP追踪等.所有的这些特性可以通过JMX或者HTTP endpoints来获得. ...

  6. Flutter Wrap 组件实现流布局

    Wrap 可以实现流布局,单行的 Wrap 跟 Row 表现几乎一致,单列的 Wrap 则跟 Row 表 现几乎一致.但 Row 与 Column 都是单行单列的,Wrap 则突破了这个限制,main ...

  7. shell编程系列1--shell脚本中的变量替换

    shell编程系列1--shell脚本中的变量替换 变量替换总结: .${变量#匹配规则} # 从头开始匹配,最短删除 .${变量##匹配规则} # 从头开始匹配,最长删除(贪婪模式) .${变量%匹 ...

  8. postgresql 利用pgAgent实现定时器任务

    1.安装pgAgent 利用Application Stack Builder安装向导,安装pgAgent. 根据安装向导一步一步安装即可. 安装完成之后,windows服务列表中会增加一个服务:Po ...

  9. [redis]带密码的客户端连接方法

    客户端连接方法: redis-cli -h localhost -p 6380 提供host为localhost,端口为6380   带密码的客户端连接方法一: redis-cli -h localh ...

  10. setInterval调用ajax回调函数不执行的问题

    setInterval调用ajax回调函数不执行 1.首先检查你的setInterval()函数写法是否正确 参考写法 // 检查是否支付成功 var isPayRequest=false; var ...