空间配置器
- 1.存在外碎片问题。(解释外碎片?)
- 2.连续的向系统向系统申请小块内存是效率低。 (进程向操作系统申请内存的过程?)
一级空间配置器
其实就是简单对malloc,realloc和free的封装,封装后对内存申请失败的情况做了相应的处理。
申请内存
- allocate封装了malloc申请内存,然后若内存申请失败时调用oom_alloc()处理。
- reallocate封装了realloc申请内存,然后若内存申请失败时调用oom_realloc()处理。
- oo_malloc()
这个函数模仿new_handler里的处理机制(引申new),采用死循环的方式,若设置了处理内存申请失败的函数则回调此函数,然后继续malloc,若再次申请失败,则循环执行上面操作,直到申请内存成功;若没有设置回调函数,则进行抛异常。
释放内存
- deallocate()
这个函数封装的free。
二级空间配置器(内碎片:在分配给对象的内部存在)
二级空间配置器主要是解决内存外碎片和频繁向内存申请小块内存效率低的情况,若申请的内存不是小块内存那么就直接调用一级空间配置器,若是小块内存这在二级空间配置器里面取;SGI库里面把这个衡量小块内存的值定为128,小于等于这个值的内存就为小块内存。
二级空间配置器结构
- 表示内存池的指针
// Chunk allocation state.
static char *start_free; //内存池开始
static char *end_free; //内存池结束
static size_t heap_size; //表示总共从系统申请了多少内存
- 自由链表
哈希桶:指针数组:自由链表
static obj * __VOLATILE free_list[__NFREELISTS];
//__NFREELISTS = 128;
内存申请
若调用的小块内存,则在二级空间配置器里的自由链表里面找有没有挂需要大小的内存块,若有则取一个返回,若没有则取内存池里面切refill(),然后把多余的挂到自由链表上。
- 直接去内存池里面切比去自由链表里面找快,那为什么有了内存池还要设计一个自由链表?
因为在内存回收的时候,内存的地址是不连续的,内存池里的内存都是连续的一大块,无法将小块内存放回内存池里,因此把这些小块内存块挂在自由链表上,这样即解决了回收问题。
- 为什么一次要从内存池里面切20块内存?
因为在多线程的情况下,多个进程去申请内存时,就会竞争,那么就需要在切内存时加锁,若不加锁可能导致申请的内存有问题。因为需要加锁,效率就比较低,所以一次切多个相同内存块挂在自由链表里,下次在申请相同大小的内存块就不用去内存池里面切。虽然申请自由链表同一下标处的内存块时也需要加锁,但是多个进程在自由链表申请不同大小的内存块时不需要加锁,提高了效率,这也是设计自由链表的优点。
- 为什么自由链表每个下标处挂的内存都是8字节的倍数?
因为每个内存块都要指向下一个内存的位置,因此最小是一个指针的大小,这里考虑到64位机,因此设计最小为8字节。
- 自由链表里面为什么可以存储大小不同内存块?
因为内存空间的大小不重要,主要看类型,类型可以进行强转,因此我们用一个obj*的类型来指向每一个下标里的内存块,自由链表下面挂的是一个个obj对象,每个下标处挂的内存大小不一样,但都是8的倍数。
内存释放
若要释放的内存大于128,那么调用一级空间配置器的deallocate;若小于等于128那么就把这个内存块直接挂只有链表的相应位置,所有申请的内存在程序结束时由操作系统释放。
refill
期望去内存池里面切20块返回
- 切一块直接返回。
- 切一块以上,返回一块,把其余的挂回到自由链表。
chunk_alloc
取内存池里切20块需要大小的内存块,计算出20块内存大小的和total。
- 1.若 total<=内存池大小
切20块返回
- 2.若 内存池大小< total <= 一块内存大小
把剩下的内存可以满足切几块就切几块,然后返回
- 3.若 内存池大小< 一块内存大小
若有内存剩下的内存挂到自由链表对应的位置,巧妙处理内存池里剩余的内存。 然后去malloc(total*2 + (heapsize>>4))大小的内存,若成功则把这些内存放到内存池里面,回调用chunk_alloc;若申请失败那么就使用最后一招调用一级空间配置器。
空间配置器的优点
- 反反复复申请和释放内存时,空间配置器快
缺点
- 内碎片
- 申请的内存在进程结束时才释放自由链表还给操作系统