在上一篇博文里提到了可以从未分配完的span里继续分配内存。那么,释放的时候怎么找到对应的内存呢。Page heap一共保存着两个map,pagemap_记录着某一内存页对应着哪一个span,pagemap_cache记录着某一内存页对应哪一个sizeclass。Pagemap_的底层是radix-tree.
现在来谈谈内存释放。具体分为小块内存释放和大块内存释放。
内存释放的过程:
1.先找出要释放的内存指针在哪个内存页上;
2.用PageID在pagemap_cache_找到对应的Sizeclass;
3.如果cache里没有对应ID,则去pagemap_找对应的span,然后得到Sizeclass;
4.取得对应线程的Threadcache,调用函数释放
最简单的就是在本地线程释放,代码如下:
inline void ThreadCache:: Deallocate(void * ptr, size_t cl )
{
FreeList* list = &list_ [cl];
size_ += Static::sizemap ()->ByteSizeForClass( cl); //检查链表大小是否超过最大限度
ssize_t size_headroom = max_size_ - size_ - 1;
ASSERT( ptr != list ->Next());
list-> Push(ptr ); //把chunk push进去
ssize_t list_headroom =
static_cast<ssize_t >(list-> max_length()) - list ->length();
//试着减减看,看下此链表长度是否超过最大容纳量
if (( list_headroom | size_headroom ) < 0) //如果长度和大小中的任意一个超过了最大值,那就处理它,让他变短
{
if (list_headroom < 0)
{
ListTooLong(list , cl);
}
if (size_ >= max_size_) Scavenge();
}
}
接下来就是处理太长了的函数,代码如下
void ThreadCache ::ListTooLong( FreeList* list , size_t cl)
{
const int batch_size = Static:: sizemap()->num_objects_to_move (cl);
ReleaseToCentralCache( list, cl , batch_size);//从线程free list弹出制定个空闲对象插入到对应中央堆去
if ( list->max_length () < batch_size)
{
list->set_max_length (list-> max_length() + 1); //动态调整max_length
//此函数为 max_length = list->max_length+1
}
else if (list ->max_length() > batch_size)
{
list->set_length_overages (list-> length_overages() + 1);
if (list ->length_overages() > kMaxOverages)
{
ASSERT(list ->max_length() > batch_size);
list->set_max_length (list-> max_length() - batch_size );
//查看有没有必要重新设置max_length
list->set_length_overages (0);
}
}
}
从中央堆释放内存
从再分配的角度看,如果直接free的内存直接放回中央堆,下次再malloc的时候又得再切割以此。所以还是老样子:先试着看能不能放进TCEntry缓存区,如果可以,放进去就返回了。如果没有地方放就只能够放到对应的span下了。
释放回span
void CentralFreeList ::ReleaseToSpans( void* object )
{
Span* span = MapObjectToSpan (object);
ASSERT( span != NULL );
ASSERT( span->refcount > 0);
if ( span->objects == NULL) //如果span 用完了就放回nonempty_ (因为本次释放了一个对象)
{
……
}
if ( false)
{
…… //assert该页里皆为空,错误报错返回
}
counter_++;
span-> refcount--;
if ( span->refcount == 0)
{
tcmalloc::DLL_Remove (span); //解锁并且将其返回到pageheap
-- num_spans_;
lock_.Unlock (); //删除记录
{
SpinLockHolder h(Static ::pageheap_lock());
Static::pageheap ()->Delete( span);
}
lock_.Lock ();
}
else
{
……
}
}
既然内存已经到了span了,那就要讨论在pageheap的释放。放到pageheap之后,会调用一个函数,即和邻近的空闲的内存合并放到空闲链表中,该函数如下:
void PageHeap ::MergeIntoFreeList( Span* span )
{
const PageID p = span-> start;
const Length n = span-> length;
Span* prev = GetDescriptor (p-1);
if ( prev != NULL && prev-> location == span ->location)
//如果前一个span(可能是单页也可能不是)就合并两个span
{
ASSERT(prev ->start + prev->length == p);
const Length len = prev->length ;
RemoveFromFreeList(prev );
DeleteSpan(prev );
span->start -= len;
span->length += len;
pagemap_.set (span-> start, span );
Event(span , 'L', len);
}
Span* next = GetDescriptor (p+ n);
if ( next != NULL && next-> location == span ->location)
//如果后一个span(可能是单页也可能不是)就合并两个span
{
ASSERT(next ->start == p+n );
const Length len = next->length ;
RemoveFromFreeList(next );
DeleteSpan(next );
span->length += len;
pagemap_.set (span-> start + span ->length - 1, span);
Event(span , 'R', len);
}
PrependToFreeList( span);
}