1.内存池的引入
实际运用当中,如果我们需要申请一块空间,我们一般都会new一块空间(在c语言里会用到malloc),我们知道他们两个的共同点就是在堆上申请空间,堆上的空间会比栈上的大一些(在windows的vs环境下,栈的默认大小是1MB,但是可以自己调整
),所以我们可以自己去申请自己需要的大小(当然也是有一定的限制)。如果我们频繁的去申请释放空间,就会形成各种各样的内存碎片,这样就造成了内存的浪费。如果我们可以将自己需要的小的空间自己保存起来(不交给操作系统管理),下次需要的时候,从自己保存的内存池子里边取,这样不仅快,还不会造成太大的浪费。
关于内存碎片:
内存碎片包括外部碎片和内部碎片,外部碎片就是,这个小的内存块的大小很长一段时间都没有找到对内存的需求小于它的作业;内部碎片就是给定一个内存块的大小,作业进入内存仅仅只需要一部分,剩下的就浪费了,浪费的部分就是内部碎片。
2.内存池的实现机制
(1)申请空间:
每次申请空间时,我们都必须优先使用上一次释放的空间,如果上一次没有释放,那么则
在内存池中找一块空间进行分配;如果上一次有进行释放,则使用上一次释放的空间,将_lastDelete(
维护最后一次释放空间的指针
)指向倒数第二次释放的空间。
注明:对于释放的空间,我们就可以合理使用,我们可以将最后一次释放的空间里存储倒数第二次释放的空间的地址,这样就涉及到一个问题:
如果一个对象的大小不足以存储一个地址(指针类型在32位系统下是4字节,在64位系统下是8字节),如果对象的大小大于指针的大小,直接分配对象的大小(释放之后,里边仍然可以存储上一次释放的内存的位置);如果对象的大小不足以指针的大小,那么就浪费一点空间,给它分配一个指针的大小。
图示:
(2)释放空间:
释放空间就是将当前不要的内存还给内存池,在这里也就是让_lastDelete指向这块要申请的空间,这块空间里存储原来
_lastDelete的值。但是,别忘了最重要的一点:
delete的实现原理:先调用析构函数,然后再还空间。
在我们简单的内存池的Delete函数中,对于内置类型析构不析构都是无所谓的,但是自定义类型(比如string)必须显式调用析构函数,然后再还空间。
我们简易的内存池---固定大小,在这样的一种情况下特别占优势:
重复的申请和释放,这样的内存池的速度是快于系统的new和delete。当多次的申请和释放,我们都是会使用之前释放的空间,就不用去和操作系统打交道。
3.实现代码
#include<iostream>
using namespace std;
#include<vector>
#include<string>
template<typename T>
class ObjectPool
{
struct BlockNode
{
void* _memory;
BlockNode* _next;
size_t _objNum;//内存对象的个数
BlockNode(size_t objNum)
:_objNum(objNum)
,_next(NULL)
{
_memory = malloc(_itemSize * _objNum);
if(_memory == NULL)
throw -1;
}
~BlockNode()
{
free(_memory);
_memory = NULL;
_objNum = 0;
}
};
protected:
size_t _countIn;//当前结点在用的计数
BlockNode* _first;
BlockNode* _last;
size_t _maxNum;
static size_t _itemSize;//单个对象的大小
T* _lastDelete;
public:
ObjectPool(size_t initNum = 32,size_t maxNum = 10000)
{
_countIn = 0;
_maxNum = maxNum;
_first = _last = new BlockNode(initNum);
_lastDelete = NULL;
}
~ObjectPool()
{
BlockNode* cur = _first;
while(cur)
{
BlockNode* del = cur;
cur = cur->_next;
delete del;
}
_first = _last = NULL;
_countIn = 0;
}
static size_t GetItemSize()
{
if(sizeof(T) < sizeof(T*))
return sizeof(T*);
else
{
return sizeof(T);
}
}
void Allocate()
{
size_t newObjectNum = _last->_objNum * 2;
if(newObjectNum >= _maxNum)
newObjectNum = _maxNum;
//开辟一个新的结点空间
_last->_next = new BlockNode(newObjectNum);
//将当前在用的计数改为
_countIn = 0;
_last = _last->_next;
}
T* New()
{
//优先使用最后一次释放的空间
if(_lastDelete)
{
T* obj = _lastDelete;
_lastDelete = *((T**)_lastDelete);
return new (obj)T;
}
//从内存块里进行申请
//所有结点的内存块都没有可以使用的,则重新开辟新的空间
if(_last->_objNum == _countIn)//最后一个结点的内存也被用完了
{
Allocate();
}
T* mem = (T*)((char*)(_last->_memory) + _countIn * _itemSize);
_countIn++;
return new(mem) T;
}
void Delete(T* ptr)
{
if(ptr)
{
ptr->~T();
}
*(T**) ptr = _lastDelete;
_lastDelete = ptr;
}
};
template<typename T>
size_t ObjectPool<T>::_itemSize = ObjectPool<T>::GetItemSize();
关于申请大小不固定内存的内存池实现,在之后的文章将与大家分享。