下面的框图展示了tcmalloc
的大致内部结构:
我们可以将 tcmalloc
分为三个部分: front-end
(前端)、middle-end
(中端)、back-end
(后端).
它们之间职责的可以粗略分为:
前端是一个缓存, 为应用程序提供快速的内存分配和释放.
中端负责重新填充(每个线程的)前端缓存.
后端则可以直接从操作系统获取内存.
前端可以处理特定大小的内存分配请求. 前端有一个内存缓存可用于分配或保存空闲内存, 该缓存由单独的线程访问. 所以它不需要任何锁, 并且大多数分配和释放速度都很快.
如果为前端设置合适的缓存大小, 理论上它可以满足任何请求. 如果特定大小的缓存耗尽, 前端会向中端请求一批内存来重新填充缓存.
如果中端缓存耗尽或者申请分配大小大于前端缓存处理的最大值, 则将请求转移到后端以满足大内存块分配, 或者重新填充前端缓存中的缓存.
当一个(内存)对象被释放时:
如果在编译时已知该对象大小,编译器将提供该对象的大小.
如果尺寸未知,则会在页面映射中查找.
如果对象很小,则会被放回到前端缓存中.
如果对象大于kMaxSize
,则直接返回到页堆.
中央空闲列表(central free list
)都被组织为两级数据结构:名为span
的集合与每个span
内的空闲对象链表.
从某个span
的链表中删除第一个条目,来达到从中央空闲列表中分配对象的目的.(如果所有span
都是空链表,则首先从中央页堆中分配一个适当大小的span
.)
通过将对象添加到其包含范围的链表来实现归还到中央空闲列表, 如果链表长度等于span
中小对象的总数,则该span
现在完全空闲并将其返回到页堆.
正确调整线程缓存的free list
大小是非常重要的! 因为当free list
太小时,会频繁的将分配请求转移到 central free list
. 而如果free list
太大,则free list
中的对象会被闲置导致内存浪费.
为了适当地调整free list
的大小,我们使用慢启动算法以确定每个单独free list
的最大长度. free list
的使用频率越高,其最大长度也会随之增加. 如果某个free list
更多地用于释放而不是分配,则它的最大长度只会增长到可以立即移动到central free list
的程度.