内存碎片
讲述内存池之前,先来说一下内存碎片的概念。物理内存实际的存储空间是连续的,内存没有被使用之前,进程刚跑起来的时候确实是顺序使用的,但是在系统中不断有进程提起,退出。这样内存在不停的被申请和释放,可用的物理内存空间不再连续,会产生很多的碎片,这就是内存碎片。
内存碎片过多,将造成内存的可用性降低,实际能用的内存没有剩余那么多,好比把一个亿分别存到一亿张银行卡一样,刷卡的时候每次只能刷一块钱的商品,有买汽车的能力却买不了,因为没有集中起来。
内存池
内存池其实就是进程先向系统申请一大块内存,然后该进程把这块内存管理起来,用的时候直接从这大块内存取,不再向系统索取,直到这大块用完才和系统申请,这样能减少操作系统的负担,提高效率。
并且,对这大块内存进行有效的管理,进程需要使用小内存时,就分配小内存块,需要连续的大内存才分配大内存块。这样有效的减少的内存碎片的产生。
一级空间配置器
其实这个很简单,就是申请的时候还是用malloc跟系统申请内存,释放的时候就释放,只不过,如果申请失败会调用一个能释放内存成功的函数,再申请内存。这个函数在一级空间配置器里用函数指针保存,通常这个指针都是为0,需要自己定义,没有定义又申请内存失败就会抛异常。
二级空间配置器
可以使用空间配置器,对内存池进行有效的管理。每次通过空间配置器向操作系统申请和释放内存,但是实际什么时候申请和释放由配置器决定,图解如下:
但是这样还无法解决内存碎片的问题,那么STL里面的空间配置器是如何解决的呢?
1.还是需要使用内存池
2.使用哈希桶,以8字节大小为小内存块,128字节为大内存块,哈希表的长度就是128/8 = 16,每个元素挂着对应内存块的大小为(下标+1)*8
3.进程申请内存,向上取8的整数倍,就直接从哈系表下面取相应大小的内存块,申请大内存用大内存块,申请小内存用小内存块,有效解决内存碎片问题。
4.假如申请15字节内存,去哈希表下标为1的元素下边拿内存块,如果已经拿完了,就去内存池取,每次去内存池取的时候都取20块16字节内存,然后挂19块,返回1块给进程。不足20块,就有多少块拿多少块。
5.如果申请的内存大于最大内存块,那就和一级空间配置器申请吧
6.如果内存池的内存也用完啦咋办呢?那就只能向操作系统再要一大块内存给内存池补点货了。但是也是存在申请失败的情况,失败了在哈希表里一直往右搜索比申请的内存更大的内存块,找到了就给了,找到最后还是没有,就去和一级空间配置器商量一下要点内存吧。
7.释放内存,其实只是把释放的内存块挂在了哈希表下面,只有超过了最大内存块的内存才会调用一级空间配置器释放。
代码
#pragma once
#include "TraceLog.h"
typedef void(*HANDLE_FUNC)();
//一级空间配置器
template <int inst>
class __MallocAllocTemplate {
private:
static void* OOM_Malloc(size_t n) //释放空间再申请空间
{
while (1)
{
if (__malloc_alloc_oom_handler == 0)
{
throw bad_alloc();
}
__malloc_alloc_oom_handler();
void *p = malloc(n);
if (p)
return p;
}
}
static HANDLE_FUNC __malloc_alloc_oom_handler;
public:
static void* Allocate(size_t n)
{
void *p = malloc(n);
if (p == NULL)
p = OOM_Malloc(n);
return p;
}
static void Deallocate(void *p, size_t /* n */)
{
free(p);
}
static HANDLE_FUNC SetMallocHandler(HANDLE_FUNC f) //给释放内存函数指针赋值
{
HANDLE_FUNC old = __malloc_alloc_oom_handler;
__malloc_alloc_oom_handler = f;
return old;
}
};
template<int inst>
HANDLE_FUNC __MallocAllocTemplate<inst>::__malloc_alloc_oom_handler = 0; //类外初始化
#include <windows.h>
void FreeHandle()
{
cout<<"释放内存"<<endl;
Sleep(1000);
}
void TestMallocAlloc()
{
__MallocAllocTemplate<0>::SetMallocHandler(FreeHandle);
void* p1 = __MallocAllocTemplate<0>::Allocate(10);
void* p2 = __MallocAllocTemplate<0>::Allocate(0x7fffffff);
}
/////////////////////////////////////////////////////////
//
//二级空间配置器(内存池)
template <bool threads, int inst>
class __DefaultAllocTemplate
{
public:
static size_t FREELIST_INDEX(size_t bytes) //查找索引
{
return (((bytes) + __ALIGN-1)/__ALIGN - 1);
}
static size_t ROUND_UP(size_t bytes) { //向上取8的倍数
return (((bytes) + __ALIGN-1) & ~(__ALIGN - 1));
}
static char* ChunkAlloc(size_t size, size_t& nobjs) //大块申请
{
size_t totalSize = size * nobjs;
size_t realSize = _endfree - _startfree;
if (realSize >= totalSize)
{
// __TRACE_DEBUG("内存池中有足够20个对象大块内存\n");
char* chunk = _startfree;
_startfree += totalSize;
return chunk;
}
else if (realSize >= size)
{
// __TRACE_DEBUG("内存池中只有%u个对象大块内存\n", nobjs);
nobjs = realSize / size;
char* chunk = _startfree;
_startfree += size*nobjs;
return chunk;
}
else
{
// __TRACE_DEBUG("内存池中一个对象都不够\n");
if (realSize > 0)
{
size_t index = FREELIST_INDEX(realSize);
((Obj*)_startfree)->_freelistlink = _freelist[index];
_freelist[index] = (Obj*)_startfree;
}
// __TRACE_DEBUG("向系统申请%ubytes字节\n", byetstoget);
//内存池没空间了,和系统申请
realSize = totalSize * 2 + ROUND_UP(_heapsize >> 4);
_startfree = (char*)malloc(realSize);
if (_startfree == NULL) //申请失败
{
//取出大于size的链表下的内存块使用
size_t index = FREELIST_INDEX(totalSize);
while (index < __NFREELISTS) //小于哈希表的长度
{
if (_freelist[index] != NULL) //找到不为空的结点下标
{
_startfree = (char*)_freelist[index]; //取出来
_freelist[index] = _freelist[index]->_freelistlink;
_endfree = _startfree + (index + 1)*__ALIGN;
return ChunkAlloc(size, nobjs);
}
index++;
}
//走到这里说明,哈希表index+后面也没有挂多余的内存了
//和系统又申请失败, 则用一级空间配置器申请
_startfree = (char*)__MallocAllocTemplate<0>::Allocate(realSize);
}
_endfree = _startfree + realSize;
_heapsize += realSize;
return ChunkAlloc(size, nobjs);
}
}
static void* Refill(size_t size) //填充
{
size_t nobjs = 20;
char* p = ChunkAlloc(size, nobjs);
if (nobjs == 1)
return p;
size_t index = FREELIST_INDEX(size);
char* cur = p;
p += size;
_freelist[index] = (Obj*)p;
for (size_t i = 0; i < nobjs-2; i++)
{
Obj* next = (Obj*)(p+size);
((Obj*)p)->_freelistlink = next;
p = (char*)next;
}
((Obj*)p)->_freelistlink = NULL;
return cur;
}
static void* Allocate(size_t n) //申请内存
{
if (n > __MAX_BYTES) //申请大小超过最大内存大小,就用一级空间配置器申请空间返回
{
return __MallocAllocTemplate<0>::Allocate(n);
}
else //申请大小小于最大内存块
{
size_t index = FREELIST_INDEX(n);
if (_freelist[index] == NULL)
{
return Refill(ROUND_UP(n)); //没有挂空间就去申请挂一些
}
else
{
Obj* cur = _freelist[index];
_freelist[index] = _freelist[index]->_freelistlink;
return cur;
}
}
}
static void Deallocate(void* p, size_t n)
{
//判断n是否大于最大内存块
if (n > __MAX_BYTES)
{
__MallocAllocTemplate<0>::Deallocate(p, n);
}
else //把释放的空间挂回哈希表,不直接还给系统
{
size_t index = FREELIST_INDEX(n);
((Obj*)p)->_freelistlink = _freelist[index];
_freelist[index] = (Obj*)p;
}
}
protected:
enum {__ALIGN = 8}; //小块内存
enum {__MAX_BYTES = 128}; //大块内存
enum {__NFREELISTS = __MAX_BYTES/__ALIGN}; //哈希表长度 128/8=16
union Obj
{
union Obj* _freelistlink; //char** 二级指针
char _client[1];
};
// 自由链表配合管理
static Obj* _freelist[__NFREELISTS];
// 内存池
static char* _startfree; //开始
static char* _endfree; //结束
static size_t _heapsize; //内存池大小
};
template <bool threads, int inst>
typename __DefaultAllocTemplate<threads, inst>::Obj*
__DefaultAllocTemplate<threads, inst>::_freelist[__NFREELISTS] = {0};
// 内存池
template <bool threads, int inst>
char* __DefaultAllocTemplate<threads, inst>::_startfree = NULL;
template <bool threads, int inst>
char* __DefaultAllocTemplate<threads, inst>::_endfree = NULL;
template <bool threads, int inst>
size_t __DefaultAllocTemplate<threads, inst>::_heapsize = 0;
#ifdef __USE_MALLOC
typedef __MallocAllocTemplate<0> alloc;
#else
typedef __DefaultAllocTemplate<false, 0> alloc;
#endif // __USE_MALLOC
void TestDefaultAlloc()
{
for(size_t i = 0; i < 21; ++i)
{
__TRACE_DEBUG("申请第%u个对象\n", i);
__DefaultAllocTemplate<false, 0>::Allocate(7);
}
}
template<class T, class Alloc>
class SimpleAlloc {
public:
static T* Allocate(size_t n)
{ return 0 == n? 0 : (T*) Alloc::Allocate(n * sizeof (T)); }
static T* Allocate(void)
{ return (T*) Alloc::Allocate(sizeof (T)); }
static void Deallocate(T *p, size_t n)
{ if (0 != n) Alloc::Deallocate(p, n * sizeof (T)); }
static void Deallocate(T *p)
{ Alloc::Deallocate(p, sizeof (T)); }
};