tcmalloc 内存池多大内存 回收

5493人阅读
C/C++(8)
Linux(20)
TCMalloc是一个比glibc2.3的malloc更快的内存管理库,通常ptmalloc2能在300纳秒执行一个malloc/free对,而TCMalloc能在50纳秒内执行一个分配对。 TCMalloc以能减少多线程程序之间的锁争用问题,在小对象上能达到零争用。 TCMalloc为每一个线程分配一个线程本地cache,少量的地址分配就直接从cache中分配,并且定期做垃圾回收,将线程本地cache中的空闲内存,返回给全局控制堆。 TCMalloc认为 size&=32K的为小对象,大对象直接从全局控制堆中以页(4K)为单位进行分配, 以就是说大对象总是页对齐的。 一个页能存入一些相同大小的小对象,实例中,一个页中存入了32个128bytes的小对象。 小对像的分配:将0-32K之间分为170个大小类别,当要申请内存时,舍入到一个合适的大小类别中。每一个线程有一个自己的内存使用链表,当分配一个小对象时,从中心内存中分配内存并加入到线程的本地内存链表中。 大对象的分配:大对象直接从中心内存堆中分配,以4K页为单位,并以连续的内存块分配 ,称为一个spens,一个spens包括连续的多个4K页. 使用:将libtcmalloc.so连接到程序中,或设置LD_PRELOAD=libtcmalloc.so
这样就可以用tcmalloc库中的函数替换系统的malloc, free, realloc, strdup内存管理函数。 环境变量:
TCMALLOC_DEBUG=&level& --调式级别取1-2
MALLOCSTATS=&level& &--设置显示内存使用状态的级别取1-2自动内存泄漏检查: TCMalloc库还能进行内存泄漏的检查,使用这个功能有两种方式,
1.将tcmalloc库连接到自己程序中,应该将tcmalloc库最后连接到程序中。
2.设置LD_PRELOAD=&libtcmalloc.so&HEAPCHECK=normal ,这样就不需要重新编译程序。 找开检查功能,有两种方式可以开关泄漏检查功能:
1.使用环境变量,这样将对整个程序进行检查,如下面,对ls进行检查
HEAPCHECK=normal &/bin/ls
2.在源代码中插入检查点的方式,这样可能控制只检查程序的某些部分
HeapProfileLeakChecker checker(&foo&); //开始检查& &
//要检查的部分& &
assert(checker.NoLeaks());
//结束检查
调用checker时,建立一个内存堆快照,在调用checker.NoLecks时,建立另一个快照,然后比较这两个快照,如果内存有增长或任意的变化,NoLecks返回false,并输入一个信息告诉你,怎么样用pprof工具来分析具体的内存泄漏。(源代码中有更多的信息) 执行内存检查:LD_PRELOAD=libtcmalloc.so HEAPCHECK=strict HEAPPROFILE=memtm ./a.out
& &执行完成后,会输入检查结果,如果有泄漏,pprof会输出泄漏了多少字节,是多少次分配的,并会输出详细的列表指出是在什么地方分配的,分配了多少次:
分配次数 &百分比 & & & & & & & & & & & & & & 泄漏代码行
1000 & & & 90.8% & &90.8% & & % subfun tm.cpp:33
&101 & & & 9.2% & &100.0% & & &101 & 9.2% obj::obj tm.cpp:15 比较两个快照:pprof --base=profile.0001.heap 程序名 profile.0020.heap文本输出方式:255.6 &24.7% &24.7% & &255.6 &24.7% GFS_MasterChunk::AddServer 列:1 - 当前对象内部使用的内存数 列:4 - 当前对象内部使用的内存及它调用的其它对象使用的内存总数 列:2,5 - 分别表示第1列和4列所用的百分比 列:3 - 为第二列的累计和
3.有时一些内存泄漏是已知的,在这些地方我们可能关闭检:
#include ...&
void *mark = HeapLeakChecker::GetDisableChecksStart();& &
&leaky code& & & & & & //不做泄漏检查的部分& &
HeapLeakChecker::DisableChecksToHereFrom(mark);
注:有些libc中的例程可能要关闭检查才能正常工作。 环境变量:
HEAPPROFILE=&pre& -- 指定内存泄漏检查的数据导出文件
HEAPCHECK=&type& &-- 堆检查类型normal,strict,draconian 注:
好象不能检测出如下的内存泄漏
char *ch = new char[1000]; & //只释放了第一个指针的情况
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:145888次
积分:1967
积分:1967
排名:第14027名
原创:43篇
转载:26篇
评论:30条
(1)(1)(1)(1)(1)(1)(2)(3)(1)(1)(2)(1)(2)(9)(1)(1)(1)(1)(2)(2)(3)(1)(2)(5)(1)(1)(2)(1)(2)(1)(1)(1)(1)(2)(4)(1)(1)(4)高分求助,tcmalloc内存泄漏的问题
[问题点数:100分]
高分求助,tcmalloc内存泄漏的问题
[问题点数:100分]
不显示删除回复
显示所有回复
显示星级回复
显示得分回复
只显示楼主
2015年9月 VC/MFC大版内专家分月排行榜第二2015年7月 硬件/嵌入开发大版内专家分月排行榜第二2014年5月 VC/MFC大版内专家分月排行榜第二2014年3月 VC/MFC大版内专家分月排行榜第二2013年10月 VB大版内专家分月排行榜第二2013年7月 VB大版内专家分月排行榜第二2012年5月 VB大版内专家分月排行榜第二2012年4月 VB大版内专家分月排行榜第二2012年2月 VB大版内专家分月排行榜第二2011年11月 VB大版内专家分月排行榜第二
2015年11月 VC/MFC大版内专家分月排行榜第三2015年6月 VC/MFC大版内专家分月排行榜第三2015年2月 VC/MFC大版内专家分月排行榜第三2014年1月 VC/MFC大版内专家分月排行榜第三2012年3月 VB大版内专家分月排行榜第三2011年12月 VB大版内专家分月排行榜第三2011年10月 VB大版内专家分月排行榜第三
2015年9月 VC/MFC大版内专家分月排行榜第二2015年7月 硬件/嵌入开发大版内专家分月排行榜第二2014年5月 VC/MFC大版内专家分月排行榜第二2014年3月 VC/MFC大版内专家分月排行榜第二2013年10月 VB大版内专家分月排行榜第二2013年7月 VB大版内专家分月排行榜第二2012年5月 VB大版内专家分月排行榜第二2012年4月 VB大版内专家分月排行榜第二2012年2月 VB大版内专家分月排行榜第二2011年11月 VB大版内专家分月排行榜第二
2015年11月 VC/MFC大版内专家分月排行榜第三2015年6月 VC/MFC大版内专家分月排行榜第三2015年2月 VC/MFC大版内专家分月排行榜第三2014年1月 VC/MFC大版内专家分月排行榜第三2012年3月 VB大版内专家分月排行榜第三2011年12月 VB大版内专家分月排行榜第三2011年10月 VB大版内专家分月排行榜第三
2015年9月 VC/MFC大版内专家分月排行榜第二2015年7月 硬件/嵌入开发大版内专家分月排行榜第二2014年5月 VC/MFC大版内专家分月排行榜第二2014年3月 VC/MFC大版内专家分月排行榜第二2013年10月 VB大版内专家分月排行榜第二2013年7月 VB大版内专家分月排行榜第二2012年5月 VB大版内专家分月排行榜第二2012年4月 VB大版内专家分月排行榜第二2012年2月 VB大版内专家分月排行榜第二2011年11月 VB大版内专家分月排行榜第二
2015年11月 VC/MFC大版内专家分月排行榜第三2015年6月 VC/MFC大版内专家分月排行榜第三2015年2月 VC/MFC大版内专家分月排行榜第三2014年1月 VC/MFC大版内专家分月排行榜第三2012年3月 VB大版内专家分月排行榜第三2011年12月 VB大版内专家分月排行榜第三2011年10月 VB大版内专家分月排行榜第三
2013年 总版技术专家分年内排行榜第三
2012年 总版技术专家分年内排行榜第七
匿名用户不能发表回复!|
每天回帖即可获得10分可用分!小技巧:
你还可以输入10000个字符
(Ctrl+Enter)
请遵守CSDN,不得违反国家法律法规。
转载文章请注明出自“CSDN(www.csdn.net)”。如是商业用途请联系原作者。tcmalloc简单分析以及glibc内存泄漏
tcmalloc简单分析以及glibc内存泄漏
最近开发一个私人程序时碰到了严重的内存问题,具体表现为:进程占用的内存会随着访问高峰不断上升,直到发生OOM被kill为止。我们使用valgrind等工具进行检查发现程序并无内存泄露,经过仔细调查我们发现时glibc的内存管理机制导致的,下次将发文对此深入解释,本文只列出核心的几个要素:
1. glibc在多线程内存分配的场景下为了减少lock
contention,会new出很多arena出来,每个线程都有自己默认的arena,但是内存申请时如果默认arena被占用,则round-robin到下一个arena。
2. 每个arena的空间不可直接共享和互相借用,除非通过主arena释放给操作系统然后被各个辅助arena重新申请。
3. glibc归还内存给OS有一个很苛刻的条件就是top
chunk必须是free的,否则,即使应用程序已经释放了大片内存,glibc也不会将这些内存归还给OS。
在我们的场景中常常是thread A alloc一片空间,最后由thread B
free,所以这就造成各个arena之间及其不平衡,加上苛刻的内存归还条件,在整个程序运行过程中,占用内存几乎从未下降过,区别仅仅是缓慢上涨和快速上涨。
由此我们实验了tcmalloc,具体介绍见:http://google-/svn/trunk/doc/tcmalloc.html
安装过程见其中的INSTALL文件,下面简略说一下:
1. install libunwind : git clone
git://git.sv.gnu.org/libunwind.git
2. download :
http://google-/files/google-perftools-1.7.tar.gz
3. ./configure --enable-frame_pointers
&& sudo make install
4. sudo ldconfig
5. g++ .... -ltcmalloc (link static lib)
tcmalloc每个线程默认最大缓存16M空间,所以当线程多的时候其占用的空间还是非常可观的,在common.h中有几个参数是控制缓存空间的,可以做合理的修改(只可个人做实验,注意法律问题):
降低每个线程的缓存空间,可以修改common.h中的kMaxThreadCacheSize,比如2M
降低所有线程的缓存空间的总大小,可以修改common.h中的kDefaultOverallThreadCacheSize,比如20M
3. 尽快将free的空间还给central list,可以将kMaxOverages改小一点,比如1
还可以定期让tcmalloc归还空间给OS,
"google/malloc_extension.h"
MallocExtension::instance()-&ReleaseFreeMemory();
实验结果证明,tcmalloc分配速度的确快,而且程序不再像以前那样内存只增不减。
上面Sanjay的文章已经对tcmalloc做了个大概的介绍,我看了一下tcmalloc的核心code,下面将其分配和释放的过程简单介绍一下:
线程申请资源:
1. 首先根据申请空间的大小从当前线程的可用内存块里面找(每个进程维护一组链表,每个链表代表一定大小的可用空间)
2. 如果step 1没有找到,则到central list里面查找(central
list跟线程各自维护的list结构很像,为不同的size各自维护一组可用空间列表)
3. 如果step 2 central
list也没有找到,则计算分配size个字节需要分配多少page(变量:class_to_pages)
4. 根据pagemap查找page对应的可用的span列表,如果找到了,则直接返回span,central
list会将该span切割成合适的大小放入对应的列表中,然后交给thread cache
5. 如果step 4没有找到可用的span,则向OS直接申请,然后步骤同step 4。
注意的是tcmalloc向系统申请空间有三种方式:sbrk,mmap,/dev/mem文件,默认是三种都try的,一种不行换另外一种。
线程释放资源:
1. 释放某个object
2. 找到该object所在的span
如果该span中所有object都被释放,则释放该span到对应的可用列表,在释放的过程中,尝试将该span跟左右spans
merge成更大的span
4. 如果当前thread cache的free 空间大于指定预置,归还部分空间给central list
5. central list也会试图通过释放可用span列表的最后几个span来将不用的空间归还给OS
tcmalloc向OS申请/释放资源是以span为单位的。
tcmalloc里面不少实现值得称道,比如pagesize到void*的mapping方式,添加/移除链表元素的时候利用结构体内存布局直接赋值,span/page/item的内存层次结构等,值得一看。
Tcmalloc源码简单分析
这段时间由于工作中涉及到内存疯长的事情,工作之余就对比分析了tcmalloc和ptmalloc的一些工作方式,关于ptmalloc代码的分析,已经有前人做了不少的工作,我这边主要讲述一下,我对tcmalloc的一点理解,写的比较随意,难免漏洞百出,有问题希望大家能够指正(email:huangjiangwei@gmail.om)开始写写还行,写到后面就不想写了,所以后面的章节代码多于评论和分析,自己都不想看下去了。
Tcmalloc通过preload或者直接动态链接的方式对malloc等内存分配和释放函数进行截获并提供服务。Tcmalloc提供接口主要涵盖malloc.h的接口。下面我将通过内存操作的基本流程,从分配开始到释放简单的分析tcmalloc的一些内部实现。
首先我们来看内存的分配。在tcmalloc中,内存分配malloc的入口为tc_malloc,new的入口为tc_new,相应的realloc,calloc,memalign,valloc等也有相应的入口。先简单描述一下tcmalloc的malloc过程和free过程。
Malloc过程
1、&&&&&&&&&&&&&&
Tcmalloc首先判断malloc的size是否大于kMaxSize(8u *
kPageSize为32k),如果小于这个值,那么将size转换为想的obj
class,然后从当前thread私有的cache中Allocate,转至第2步。如果请求的size大于kMaxSize那么跳至第10。
2、&&&&&&&&&&&&&&
首先判断当前的threadcache中obj
calss对应的freelist中是否包含有空闲的obj,如果有直接pop出来,否则从CentralCache中拿,转下一步。
3、&&&&&&&&&&&&&&
CentralCache和ThreadCache之间obj的转移采用batch方式,每次转移固定数量的obj,这个数量通过Static::sizemap()-&num_objects_to_move定义,当然在决定最终转移数量时还是需要不能超过ThreadCache相应list的maxlength。然后通过CentralCache对应freelist的RemoveRange函数将确定大小的obj转移出来,并通过对应list的PushRange函数将这些obj插入ThreadCache对应的freelist。
4、&&&&&&&&&&&&&&
CentralCache通过RemoveRange将特定数量的obj移出,CentralCache将连续的内存看做一个Span,Span是CentralCache管理内存的一个主要数据结构。而Span又被切分成N个统一大小的obj。每个CentralCache管理属于某个特定class的obj。在同一个CentralCache中所包含的obj大小符合特定calss的要求。CentralCache包含kNumTransferEntries(kNumClasses)个slots,这些slots用来存储正好包含num_objects_to_move个obj的Span。如果slots满了或者包含其他数量obj的Span将统一放入两个Span队列,empty和nonempty。这里的名字比较诡异empty函数存放的Span不是空闲的Span而是Span里面没有空闲obj的Span,nonempty则相反。
5、&&&&&&&&&&&&&&
因此在Allocate的过程中,首先判断需要Allocate的obj数量是不是正好符合num_objects_to_move,如果是而且CentralCache用来存放span的slots不为空,那么直接从slots里面拿,否则从nonempty队列中的Span拿。
6、&&&&&&&&&&&&&&
Nonempty队列存放了所有可用的Span,那么从头开始一个个拿,如果拿光了还是不能满足要求,那么只能通过向pageheap要求一个span,这个span的size由class_to_pages决定,然后再将这个Span切成obj返回给CentralCache。然后再次尝试从Span分配。
7、&&&&&&&&&&&&&&
Pageheap管理整个系统page级别的allocate,他通过两个数据结构管理所有的Span(free_数组和large_列表),free_数组存放size小于kMaxPages(1
kPageShift)256)的Span,而large_列表存放大于等于kMaxPages的Span。PageHeap首先判断要求的pages是否大于等于kMaxPages,如果小于那么先从free数组中找,从要求大小的位置开始往后找,先找normal队列在找return对队列。如果在normal队列中找到且找到的Span状态为Span::ON_NORMAL_FREELIST,那么直接从里面切出需要的Span返回给CentralCache。如果在return队列中找到且找到的Span状态为Span::
ON_RETURNED_FREELIST那么直接从里面切出需要的Span返回给CentralCache。
8、&&&&&&&&&&&&&&
如果需要的size不符合上述要求或者在上述队列中没有找到那么将从large_队列中找。从large_队列中查找时,首先从normal队列入手,然后再从return队列找,他将找到size最符合且地址在空闲Span中最小的Span,然后切出来返回。
9、&&&&&&&&&&&&&&
如果large_队列中都没有找到合适的Span,那么将通过GrowHeap增长Heap的方式,通过TCMalloc_SystemAlloc向系统申请不小于1M的内存。并包装成Span,并插入heap中,然后再次进行分配。
10、&&&&&&&&&&
来到此处代表分配的内存是大于32k的,那么将向heap直接请求跳到第7步。
1、&&&&&&&&&&&&
首先free根据提供的ptr指针,获得此ptr所在的页面,然后利用页面号,获取需要释放的obj的size,这个size首先从pagemap_cache_中拿,如果不在pagemap_cache_中那么直接通过页面好获取所在的span,如果span为NULL,那么直接报错返回,否则通过span获取需要释放的obj的size,并将size和page的关系放入pagemap_cache_。
2、&&&&&&&&&&&&
如果能够获取ptr所指obj的size,且本释放是在子线程内(可以获取threadcache),那么调用threadcache的Deallocate释放,转到一下步。否则利用InsertRange直接将obj插入Centralcache对应size的freelist。如果ptr所指的obj
size为0,有可能是大的obj,直接从pageheap中拿的,那么跳第5步。
3、&&&&&&&&&&&&
Threadcache通过相应的size获得对应的freelist,然后通过push将obj链入freelist。如果插入之后的list太长将通过ReleaseToCentralCache将num_objects_to_move个obj返还给CentralCache,如果整个threadcache的size太大,那么将对整个free数组进行遍历,调用ReleaseToCentralCache函数。
4、&&&&&&&&&&&&
ReleaseToCentralCache将通过对应CentralCache的freelist调用InsertRange将需要返还给CentralCache的objs插入CentralCache。CentralCache判断所返还的obj数量是不是正好等于num_objects_to_move,如果是且slots_里面有空闲区,那么直接返还给slots_,否则将这些obj返回给相应的Span,如果本Span通过返还之后已经是有freeobj了,将此Span从empty_列表转移到nonempty_列表,如果此Span已经全部空闲了,没有还在被使用的obj,那么通过Static::pageheap()-&Delete返还给pageheap。
5、&&&&&&&&&&&&
Pageheap通过Delete函数将返还回来Span的相关参数清零,并设置当前Span为ON_NORMAL_FREELIST。然后判断此Span能不能和前后相邻的Span合并,然后将Span插入相应size的normal队列,最后判断是否需要回收,如果需要,尝试回收至少一个页面,回收动作主要讲Span从normal列表转移到return列表,并调用TCMalloc_SystemRelease将页面标记为可回收的,然后通过系统调用madvise(reinterpret_cast&char*&(new_start),
new_start,&&&&&&&&&&&&&&&&&&
MADV_DONTNEED)将物理内存返回给系统,而保留虚拟内存。
已投稿到:
以上网友发言只代表其个人观点,不代表新浪网的观点或立场。TCMalloc源码学习(三)(小块内存分配)
TCMalloc源码学习(三)(小块内存分配)
线程本地cache线程本地cache对应的是类 ThreadCache,每一个thread一个实例,初始化代码在static函数CreateCacheIfNecessary中, 在该线程第一次申请内存的时候初始化,调用堆栈是 :1
tcmalloc::ThreadCache::CreateCacheIfNecessary()
tcmalloc::ThreadCache::GetCache()
do_malloc_no_errno(unsigned int size)
do_malloc(unsigned int size)
do_malloc_or_cpp_alloc(unsigned int size)
tc_malloc(unsigned int size)CreateCacheIfNecessary代码如下: 1 ThreadCache* ThreadCache ::CreateCacheIfNecessary() {
3 // Initialize per-thread data if necessary
5 ThreadCache* heap = NULL ;
9 SpinLockHolder h (Static:: pageheap_lock());
11 // On some old glibc's, and on freebsd's libc (as of freebsd 8.1),
13 // calling pthread routines (even pthread_self) too early could
15 // cause a segfault.
Since we can call pthreads quite early, we
17 // have to protect against that in such situations by making a
19 // 'fake' pthread.
This is not ideal since it doesn't work well
21 // when linking tcmalloc statically with apps that create threads
23 // before main, so we only do it if we have to.
25 #ifdef PTHREADS_CRASHES_IF_RUN_TOO_EARLY
27 pthread_
29 if (!tsd_inited_ ) {
31 memset(&me , 0, sizeof( me));
35 me = pthread_self ();
41 const pthread_t me = pthread_self();
45 // This may be a recursive malloc call from pthread_setspecific()
47 // In that case, the heap for this thread has already been created
49 // and added to the linked list.
So we search for that first.
51 for (ThreadCache * h = thread_heaps_; h != NULL; h = h -&next_) {
53 if (h -&tid_ == me) {
63 if (heap == NULL) heap = NewHeap (me);
67 // We call pthread_setspecific() outside the lock because it may
69 // call malloc() recursively.
We check for the recursive call using
71 // the "in_setspecific_" flag so that we can avoid calling
73 // pthread_setspecific() if we are already inside pthread_setspecific().
75 if (! heap-&in_setspecific_ && tsd_inited_) {
77 heap-&in_setspecific_ = true;
79 perftools_pthread_setspecific(heap_key_ , heap);
81 #ifdef HAVE_TLS
83 // Also keep a copy in __thread for faster retrieval
threadlocal_data_. heap =
87 SetMinSizeForSlowPath(kMaxSize + 1);
91 heap-&in_setspecific_ = false;
99 代码都有详细注释,简单描述就是所有线程的 ThreadCache 用链表串起来了,thread_heaps_指向链表头,NewHeap往前插节点;如果平台有支持TLS(比如windows),会把heap保存到一个TLS变量中,这样取当前线程的ThreadCache的时候比用& pthread_getspecific() 要高效。线程本地cache分配内存通过接口 ThreadCache::Allocate分配对应size的内存,代码如下: 1 inline void * ThreadCache:: Allocate(size_t size, size_t cl ) {
3 ASSERT(size &= kMaxSize);
5 ASSERT(size == Static:: sizemap()-&ByteSizeForClass (cl));
7 FreeList* list = &list_[ cl];
9 if (list -&empty()) {
11 return FetchFromCentralCache (cl, size);
15 size_ -=
17 return list -&Pop();
20 ThreadCache的list_是一个FreeeList的数组,每一项代表对应size class当前可用的空闲内存;FreeeList在内存布局上来看是一大段连续内存,然后按照对应size划分了一个个object节点,节点之间彼此链接起来,节点并没有像通常的list一样有显示定义next,而是把节点内存的前面几个字节保存了下一个节点的地址,所有实现都是围绕void *,看起来非常简洁,比如取下个节点的地址和设置下个节点代码: 1 inline void *SLL_Next( void *t ) {
3 return *( reinterpret_cast&void **&(t));
7 inline void SLL_SetNext( void *t , void * n) {
*(reinterpret_cast& void**&(t )) =
12 Allocate判断list是不是空,若不空直接从list链表头弹出一个object。否则需要从Central Cache去请求一次分配。 1 // Remove some objects of class "cl" from central cache and add to thread heap.
3 // On success, return the first obje otherwise return NULL.
5 void* ThreadCache ::FetchFromCentralCache( size_t cl , size_t byte_size) {
7 FreeList* list = &list_[ cl];
9 ASSERT(list -&empty());
11 const int batch_size = Static::sizemap ()-&num_objects_to_move( cl);
13 const int num_to_move = min&int &(list-& max_length(), batch_size );
15 void *start , *
17 int fetch_count = Static:: central_cache()[cl ].RemoveRange(
& start, &end , num_to_move);
21 ASSERT(( start == NULL ) == (fetch_count == 0));
23 if (-- fetch_count &= 0) {
25 size_ += byte_size * fetch_
27 list-&PushRange (fetch_count, SLL_Next(start ), end);
31 // Increase max length slowly up to batch_size.
After that,
33 // increase by batch_size in one shot so that the length is a
35 // multiple of batch_size.
37 if ( list-&max_length () & batch_size) {
39 list-&set_max_length (list-& max_length() + 1);
43 // Don't let the list get too long.
In 32 bit builds, the length
45 // is represented by a 16 bit int, so we need to watch out for
47 // integer overflow.
49 int new_length = min& int&(list -&max_length() + batch_size,
51 kMaxDynamicFreeListLength);
53 // The list's max_length must always be a multiple of batch_size,
55 // and kMaxDynamicFreeListLength is not necessarily a multiple
57 // of batch_size.
59 new_length -= new_length % batch_
61 ASSERT(new_length % batch_size == 0);
63 list-&set_max_length (new_length);
71 FetchFromCentralCache 便是ThreadCache向Central Cache请求分配内存的函数,代码如下:简单来说,从SizeMap获取一次分配应该获得的objects的个数,然后把这么多内存从central cache中移动到线程本地free list中,最后就是更新free list最大可能长度这个属性,每次分配增大最大可能长度,让本地线程频繁使用的object 内存所在的空闲链表能容纳更多object。从central cache分配内存ThreadCache的每一个FreeeList都有其对应的CentralFreeeList,从Centreal Cache移动内存到ThreadCache就是通过CentralFreeList的RemoveRange接口来完成的,代码如下: 1 int CentralFreeList ::RemoveRange( void **start , void ** end, int N) {
3 ASSERT( N & 0);
5 lock_. Lock();
7 if ( N == Static ::sizemap()-& num_objects_to_move(size_class_ ) &&
9 used_slots_ & 0) {
11 int slot = --used_slots_;
13 ASSERT(slot &= 0);
15 TCEntry *entry = &tc_slots_[ slot];
* start = entry -&
* end = entry -&
21 lock_.Unlock ();
23 return N ;
27 int result = 0;
29 void* head = NULL ;
31 void* tail = NULL ;
33 // TODO: Prefetch multiple TCEntries?
35 tail = FetchFromSpansSafe();
37 if ( tail != NULL ) {
39 SLL_SetNext(tail , NULL);
43 result = 1;
45 while (result & N) {
47 void *t = FetchFromSpans();
49 if (!t ) break;
51 SLL_Push(&head , t);
53 result++;
59 lock_. Unlock();
69 因为CentralFreeList是所有线程共享了,所以操作的时候要锁住先。另外,针对内存在CentralFreeList和ThreadCache的FreeList频繁移动的free list,CentralFreeList又维护了一个转移缓存TCEntry,每次RemoveRange 先判断该缓存中有没,有则直接返回,否则要从Span获取。SpanSpan是什么,他标识一段连续的内存页,他可以作为节点和其他Span串起来,他可以把内存页划分成一个个objects供分配小块内存,他定义为如下的结构体: 1 struct Span {
// Starting page number
// Number of pages in span
// Used when in link list
// Used when in link list
// Linked list of free objects
17 &CentralFreeList有两个Spans链表:Span empty_;&&&&&&&&& // Dummy header for list of empty spansSpan nonempty_;&&&&&& // Dummy header for list of non-empty spans回到之前的RemoveRange,如果转移缓存没有命中,就会转而调用FetchFromSpansSafe: 1 void* CentralFreeList ::FetchFromSpansSafe() {
3 void * t = FetchFromSpans ();
5 if (! t) {
7 Populate();
9 t = FetchFromSpans ();
17 先试着从FetchFromSpans获取: 1 void* CentralFreeList ::FetchFromSpans() {
if ( tcmalloc::DLL_IsEmpty (&nonempty_)) return NULL ;
Span* span = nonempty_ .
ASSERT( span-&objects != NULL);
span-& refcount++;
void* result = span -&
span-& objects = *(reinterpret_cast &void**&( result));
if ( span-&objects == NULL) {
// Move to empty list
tcmalloc::DLL_Remove (span);
tcmalloc::DLL_Prepend (&empty_, span);
Event(span , 'E', 0);
counter_--;
18 nonempty_放的是那些非空的span(即标识的内存还未分配完),FetchFromSpans每次Fetch一个object,span的引用计数随即加1,若span标识的内存都分配完了,则把该span从nonempty_移动到empty_。如果nonempty_也是空的,就要从page heap中获取内存了,Populate函数就是用来从page heap申请内存的: 1 // Fetch memory from the system and add to the central cache freelist.
3 void CentralFreeList ::Populate() {
5 // Release central list lock while operating on pageheap
7 lock_. Unlock();
9 const size_t npages = Static:: sizemap()-&class_to_pages (size_class_);
15 SpinLockHolder h(Static ::pageheap_lock());
17 span = Static ::pageheap()-& New(npages );
19 if (span ) Static:: pageheap()-&RegisterSizeClass (span, size_class_);
23 if ( span == NULL ) {
25 Log(kLog , __FILE__, __LINE__,
27 "tcmalloc: allocation failed" , npages && kPageShift);
29 lock_.Lock ();
31 return;
35 ASSERT( span-&length == npages);
37 // Cache sizeclass info eagerly.
Locking is not necessary.
39 // (Instead of being eager, we could just replace any stale info
41 // about this span, but that seems to be no better in practice.)
43 for ( int i = 0; i & i ++) {
45 Static::pageheap ()-&CacheSizeClass( span-&start + i, size_class_);
49 // Split the block into pieces and add to the free-list
51 // TODO: coloring of objects to avoid cache conflicts?
53 void** tail = &span -&
55 char* ptr = reinterpret_cast &char*&( span-&start && kPageShift);
57 char* limit = ptr + (npages && kPageShift);
59 const size_t size = Static:: sizemap()-&ByteSizeForClass (size_class_);
61 int num = 0;
63 while ( ptr + size &= limit) {
67 tail = reinterpret_cast &void**&( ptr);
75 ASSERT( ptr &= limit );
*tail = NULL;
79 span-& refcount = 0; // No sub-object in use yet
81 // Add span to list of non-empty spans
83 lock_. Lock();
85 tcmalloc:: DLL_Prepend(&nonempty_ , span);
++num_spans_;
89 counter_ +=
93 SizeMap决定一次从page heap分配的内存页数。操作page heap同样需要另一把锁,从page heap New出来的内存同样也是用Span标识,分配出来后还要经过切分处理,按照对应size把内存切分成一个个objects,彼此之间链接起来,最后Span保存链表头指针,用于之后的分配。切分完成,就把该新span放入nonempty_等待分配。从 Page Heap分配内存PageHeap维护一个free_(SpanList数组,SpanList被定义成两个Span链表),第n项代表长度为n页的内存,free_[i]里面再分成了两条可用链表,一种是普通的可用内存,一种是返回给系统的内存。从PageHeap分配内存是通过PageHeap::New接口: 1 Span* PageHeap::New(Length n) {
ASSERT(Check());
ASSERT(n & 0);
Span* result = SearchFreeAndLargeLists(n);
9 if (result != NULL)
13 if (stats_.free_bytes != 0 && stats_.unmapped_bytes != 0
&& stats_.free_bytes + stats_.unmapped_bytes &= stats_.system_bytes / 4
&& (stats_.system_bytes / kForcedCoalesceInterval
!= (stats_.system_bytes + (n && kPageShift)) / kForcedCoalesceInterval)) {
21 // We're about to grow heap, but there are lots of free pages.
23 // tcmalloc's design decision to keep unmapped and free spans
25 // separately and never coalesce them means that sometimes there
27 // can be free pages span of sufficient size, but it consists of
29 // "segments" of different type so page heap search cannot find
31 // it. In order to prevent growing heap and wasting memory in such
33 // case we're going to unmap all free pages. So that all free
35 // spans are maximally coalesced.
39 // We're also limiting 'rate' of going into this path to be at
41 // most once per 128 megs of heap growth. Otherwise programs that
43 // grow heap frequently (and that means by small amount) could be
45 // penalized with higher count of minor page faults.
49 // See also large_heap_fragmentation_unittest.cc and
51 // /p/gperftools/issues/detail?id=368
ReleaseAtLeastNPages( static_cast&Length&(0x7fffffff));
55 // then try again. If we are forced to grow heap because of large
57 // spans fragmentation and not because of problem described above,
59 // then at the very least we've just unmapped free but
61 // insufficiently big large spans back to OS. So in case of really
63 // unlucky memory fragmentation we'll be consuming virtual address
65 // space, but not real memory
result = SearchFreeAndLargeLists(n);
69 if (result != NULL) return
73 // Grow the heap and try again.
75 if (!GrowHeap(n)) {
ASSERT(Check());
79 return NULL;
83 return SearchFreeAndLargeLists(n);
87 整个实现先从空闲内存获取,SearchFreeAndLargeLists如下: 1 Span* PageHeap ::SearchFreeAndLargeLists( Length n ) {
3 ASSERT( Check());
5 ASSERT( n & 0);
7 // Find first size &= n that has a non-empty list
9 for ( Length s = s & kMaxP s++) {
11 Span* ll = &free_[ s].
13 // If we're lucky, ll is non-empty, meaning it has a suitable span.
15 if (!DLL_IsEmpty (ll)) {
17 ASSERT(ll -&next-& location == Span ::ON_NORMAL_FREELIST);
19 return Carve (ll-& next, n );
23 // Alternatively, maybe there's a usable returned span.
25 ll = &free_ [s].
27 if (!DLL_IsEmpty (ll)) {
29 // We did not call EnsureLimit before, to avoid releasing the span
31 // that will be taken immediately back.
33 // Calling EnsureLimit here is not very expensive, as it fails only if
35 // there is no more normal spans (and it fails efficiently)
37 // or SystemRelease does not work (there is probably no returned spans).
39 if (EnsureLimit (n)) {
41 // ll may have became empty due to coalescing
43 if (!DLL_IsEmpty (ll)) {
45 ASSERT(ll -&next-& location == Span ::ON_RETURNED_FREELIST);
47 return Carve (ll-& next, n );
57 // No luck in free lists, our last chance is in a larger class.
59 return AllocLarge(n );
// May be NULL
63 在大于等于n页的空闲内存链表里面开始搜索内存对应的Span,先是normal链表再是returned链表,如果找到了一个Span,可能是在一个大于n页的内存里面找到的,所以要切割一下,Carve函数主要是把多余的内存切割出来再放回空闲链表。如果在返还给系统的内存中找到合适的Span,相当于要从系统申请内存了,EnsureLimit要确保从系统申请的内存不能超过上限。如果仍然没有找到可用的Span,就要跳转到AllocLarge去获取了,PageHeap还有一个larger_ (SpanList类型),对于页数大于kMaxPages的内存不是放在free_而是larger_,从中查找合适Span的过程也是类似free_[i]链表,只不过larger_里面的Span链表链接起来的Spans标识的内存不一定都是一样的内存大小。以上还是找不到空闲的内存,回到PageHeap::New,考虑到free的内存和unmmaped的内存(既在normal spans list中标识的内存和returned spans list中标识的内存)可能有足够多的小块页内存,于是可以把free的内存都释放(PageHeap ::ReleaseAtLeastNPages调用,后面可以看到,其实成功释放回给系统的内存都会放到returned spans list中),释放过程其实也会有和邻近的页内存合并的过程(在PageHeap::MergeIntoFreeList),这样就达到了把所有可用的小块内存在returned spans list中进行合并的目的,合并完后再进行一次可用内存搜索(PageHeap :: SearchFreeAndLargeLists调用)。最后,这时实在找不到可用内存了就扩充堆的大小( PageHeap:: GrowHeap调用),GrowHeap从系统获取指定大小的内存(页对齐的)。这里有一个问题,为什么成功释放回给系统的内存,还可以放在returned spans list中并供下次分配直接使用?看释放一个Span内存的代码: 1 Length PageHeap ::ReleaseLastNormalSpan( SpanList* slist ) {
3 Span* s = slist -&normal.
5 ASSERT( s-&location == Span:: ON_NORMAL_FREELIST);
7 if ( TCMalloc_SystemRelease(reinterpret_cast &void*&( s-&start && kPageShift),
9 static_cast&size_t &(s-& length && kPageShift ))) {
11 RemoveFromFreeList(s );
13 const Length n = s-&
15 s-&location = Span:: ON_RETURNED_FREELIST;
17 MergeIntoFreeList(s );
// Coalesces if possible.
23 return 0;
26 关键是在TCMalloc_SystemRelease 的实现中。比如在linux平台,TCMalloc_SystemRelease的实现使用MADV_DONTNEED参数调用madivse,告诉系统这一段内存没有再引用了你可以释放与其相关的资源,具体来说应该是可以把这些页对应的物理内存换出了,而且还有一个特性,那就是下次你访问这个释放的内存的时候还是能够直接访问而不需要其他系统调用,只不过系统这时候需要重新加载这段内存,这个特性才使得TCMalloc能够把已经释放的内存保存起来作为可用内存(不过不是优先使用的)。& MADV_DONTNEED&&&&&&&&&&&&&& Do& not& expect& access& in the near future.& (For the time being, the application is finished with the given range, so the kernel can&&&&&&&&&&&&&& free resources associated with it.)& Subsequent accesses of pages in this range will succeed, but will result either in re-loading& of&&&&&&&&&&&&&& the& memory& contents& from& the& underlying mapped file (see mmap(2)) or zero-fill-on-demand pages for mappings without an underlying&&&&&&&&&&&&&& file.但是在windows平台,TCMalloc_SystemRelease没有任何实现直接返回了false(估计是没有madivse这种系统调用),也就是在windows上TCMalloc所占用的系统内存只会增加不会减少,而且returned spans list也是不可用的。这样看起来成为了某种意义上的内存泄漏了,不过所幸有PageHeap::EnsureLimit,这个函数确保可以配置从系统申请的内存上限,超过上限就返回false,每次申请内存的操作都会调用EnsureLimit确保内存增长在一个可控范围内。总结:纵观TCMalloc小内存分配,内存块的流转都是在 ThreadCache,CentralFreeList,PageHeap之间进行,每一个区块代表一个cache,每一个cache有不同表达形式的内存,内存申请逐一而上,层次分明。整体设计看起来很简单,但是却有许多细节和优化。数据结构Spans构思巧妙实现简洁,是标识内存非常好的创意,后面可以单独画图分析。
发表评论:
TA的最新馆藏

我要回帖

更多关于 tcmalloc 内存泄露 的文章

 

随机推荐