(1)第一层,线程局部分配,ThreadCache
ThreadCache包含了一个不同对象大小的空闲链表数组,其实现采用操作系统的线程局部存储功能。分配时几乎不需要用锁,除非触发CentralCache的操作。
ThreadCache中的重要数据结构:
pthread_t tid_; 绑定线程,达到每个线程有个缓冲池的目的
FreeList list_[kNumClasses]; 这个数组就是上图中的第一列,如下图
数组中的每一个节点就是代表上图中的每一行,如下图
每个class对应多大的内存空间?这个表示每组的大小的变量在哪里?
不存在这样的变量,但是通过映射关系可以达到一个class管一类size的作用,
如下图所示,由cl 得到list_[cl],这也即是一个class。
至于cl,是由class_array_得到的,关于这个内容在第五章:几个重要的数据结构
若申请的内存是13字节,但分配的却是15字节,那么便会有2个字节的内存碎片(内部碎片)。
(2)第二层 ,中心分配,Centralcache
该层的分配需要锁。CentralCache和ThreadCache之间的空闲链表是一一对应的,以子链表为单位(obj个数很可能为num_objects_to_move(cl),见do_malloc与do_free流程图)进行互相交换。
CentralCache的内存从PageHeap里获得。从PageHeap获得的内存叫Span。一个Span在使用时只能用于同一大小的空闲链表,一但CentralCache从PageHeap中获取新的Span,这个Span就是一个串好的相同大小内存的空闲链表。
Centralcache中有几个重要数据结构:
1. TCEntry tc_slots_[kMaxNumTransferEntries];
tc_slots_每个节点存放的是一组obj链表,这一组obj的个数为num_objects_to_move,TCEntry结构体有两指针,分别指向这个链表的头和尾。
tc_slots_存放的是threadCache向CentralCache归还的obj链表,并且只有当个数满足num_objects_to_move时,才会放入tc_slots_。否则归还的obj根据其所处的span,进行归还,若对应的span是empty,那么由于此时被归还内存了,所以其有空闲obj了,便把该span从empty队列清除,把其加入nonempty队列。
2. span empty
FetchFromSpans函数把一个obj从nonempty队列中的一个span中切出,准备给threadCache。当切完这个obj后,如果该span已经没有内存空间了,那么便把该span从nonempty队列移除,并加入empty队列。
3. span nonempty
CentralCache从中央页堆申请页面,中央页堆以span的形式返回。在CentralCache中会把该span切成大小为class_to_size的obj,并把所有的obj链接起来,链表头为span->objects。再把该链表加入nonempty队列。
Nonempty队列另外一个被加入span的地方在内存从threadCache归还给CentralCache时,具体情况见上面”tc_slots_”这一数据结构的描述。
(3)第三层,中央页堆,PageHeap
PageHeap以一定数量连续页面内存的形式提供内存。这组连续的页面由一个Span对象描述,Span对象和它描述的页面内存是独立的。Span对象保存了页面的id序列,页面id左移一个page就是内存的地址。由于页面和Span内存独立,需要用page id反向映射查找Span对象就需要单独的映射表。这个表用radix tree实现,兼顾效率和内存。PageHeap还负责合并和拆分相邻的Span。
PageHeap重要数据结构:
SpanList large_;
SpanList free_[kMaxPages];
中央页堆是由空闲内存列表组成的数组。对于i< 256而言,数组的第k个元素是一个由每个单元是由k个页面组成的空闲内存链表(这也即是free_)。第256个条目(这即是large_)则是一个包含了长度>= 256个页面的空闲内存链表:
而SpanList为
struct SpanList {
Span normal;
Span returned;
};
Returned代表的是已经归还给系统的span
(4)第四层,系统页面分配,
这就是调用系统函数了。