内存回收
- 1.ThreadCache
- 2.CentralCache
- 3.PageCache
项目源代码:高并发内存池
1.ThreadCache
void ThreadCache::Deallocate(void* ptr, size_t size)
{
assert(ptr);
assert(size <= MAX_BYTES);
//计算在哪号桶中,然后插入进去
size_t index = SizeClass::Index(size);
_freeLists[index].Push(ptr);
//当链表长度大于一次批量申请的内存时就开始还一段list给central cache
if (_freeLists[index].Size() >= _freeLists[index].MaxSize())
{
ListTooLong(_freeLists[index], size);
}
}
void ThreadCache::ListTooLong(FreeList& list, size_t size)
{
void* start = nullptr;
void* end = nullptr;
list.PopRang(start, end, list.MaxSize());
CentralCache::GetInstance()->ReleaseListToSpans(start, size);
}
当闲置的内存超过一个批量单位大小的时候就开始回收,首先要计算出要回收到哪个桶的的内存,然后逐级往上回收。
2.CentralCache
void CentralCache::ReleaseListToSpans(void* start, size_t size)
{
size_t index = SizeClass::Index(size);
_spanLists[index]._mtx.lock();
while (start)
{
void* next = Nextobj(start);
Span* span = PageCache::GetInstance()->MapObjectToSpan(start);
Nextobj(start) = span->_freeList;
span->_freeList = start;
span->_usecount--;
if (span->_usecount == 0)
//说明span切分出去的内存小块都回收回来了,
//这时这个span就可以再回收给page cache,page cache可以再尝试去做前后页的合并
{
_spanLists[index].Erase(span);
span->_freeList = nullptr;
span->_prev = nullptr;
span->_next = nullptr;
//释放span给page cache时,使用page cache的锁就可以了
//所以需要先把桶锁解掉再加page cache的大锁
_spanLists[index]._mtx.unlock();
PageCache::GetInstance()->_pageMtx.lock();
PageCache::GetInstance()->ReleaseSpanToPageCache(span);
PageCache::GetInstance()->_pageMtx.unlock();
_spanLists[index]._mtx.lock();
}
start = next;
}
_spanLists[index]._mtx.unlock();
}
CentralCache回收回来还需要做前后页的合并,合成一个大的内存块,然后继续交给PageCache处理
3.PageCache
void PageCache::ReleaseSpanToPageCache(Span* span)
{
//大于128页的span,直接还给堆
if (span->_n > NPAGES -1)
{
void* ptr = (void*)(span->_pageId << PAGE_SHIFT);
SystemFree(ptr);
//delete span;
_spanPool.Delete(span);
return;
}
//对span前后的页,尝试进行合并,缓解内存碎片问题(外碎片)
//对前后的页进行合并
while (1)
{
PAGE_ID prevId = span->_pageId - 1;
//auto ret = _idSpanMap.find(prevId);
前面的页号没有找到,不进行合并
//if (ret == _idSpanMap.end())
//{
// break;
//}
auto ret = (Span*)_idSpanMap.get(prevId);
if (ret == nullptr)
{
break;
}
//前面相邻页的span在使用,不进行合并
Span* prevSpan = ret;
if (prevSpan->_isUse == true)
{
break;
}
//合并出超过128的span没办法管理,就不能继续合并
if (prevSpan->_n + span->_n > NPAGES - 1)
{
break;
}
//合并
span->_pageId = prevSpan->_pageId;
span->_n += prevSpan->_n;
_spanList[prevSpan->_n].Erase(prevSpan);
//delete prevSpan;
_spanPool.Delete(prevSpan);
}
//向后合并
while (1)
{
PAGE_ID nextId = span->_pageId + span->_n;
/*auto ret = _idSpanMap.find(nextId);
if (ret == _idSpanMap.end())
{
break;
}*/
auto ret = (Span*)_idSpanMap.get(nextId);
if (ret == nullptr)
{
break;
}
Span* nextSpan = ret;
if (nextSpan->_isUse == true)
{
break;
}
if (span->_n + nextSpan->_n > NPAGES - 1)
{
break;
}
span->_n += nextSpan->_n;
_spanList[nextSpan->_n].Erase(nextSpan);
//delete nextSpan;
_spanPool.Delete(nextSpan);
}
_spanList[span->_n].PushFront(span);
span->_isUse = false;
//_idSpanMap[span->_pageId] = span;
_idSpanMap.set(span->_pageId, span);
//_idSpanMap[span->_pageId + span->_n - 1] = span;
_idSpanMap.set(span->_pageId + span->_n - 1, span);
}
PageCache需要将一页一一页的小块内存何合并成一张大页的内存,来解决内存碎片问题,因为大的可以切成小的,而当申请的内存大于小块的内存碎片时,就会向堆中申请,造成内存浪费。
点赞支持~