TCMalloc 的全局分配器,处于 TCMalloc 的最底层,负责向操作系统申请和释放内存,接口有两个,定义在 src/system-alloc.h|.cc:
1 2 3 | extern void* TCMalloc_SystemAlloc(size_t bytes, size_t *actual_bytes, size_t alignment = 0); extern bool TCMalloc_SystemRelease(void* start, size_t length); |
TCMalloc_SystemAlloc 的参数,除了要申请的大小 bytes,还有 actual_bytes 和 alignment,因为对齐的需求,该接口可能分配大于 bytes 的内存,实际大小保存在 actual_bytes。TCMalloc_SystemRelease 负责『释放』内存,至于释放为什么加引号,下面会提到。接下来,介绍下这两个接口的实现。
malloc-extension.h 里面,定义了接口类 SysAllocator,该类只有一个接口:
1 2 3 4 5 6 | class SysAllocator { public: SysAllocator() {} virtual ~SysAllocator(); virtual void* Alloc(size_t size, size_t *actual_size, size_t alignment) = 0; }; |
system-alloc.cc 里面,对 SysAllocator 有三个实现:SbrkSysAllocator, MmapSysAllocator, DefaultSysAllocator。SbrkSysAllocator 和 MmapSysAllocator 分别使用 sbrk 和 mmap 向系统分配内存,这两个类没有任何数据成员。DefaultSysAllocator 是 前两种 Allocator 的委托:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | class SbrkSysAllocator : public SysAllocator { public: SbrkSysAllocator() : SysAllocator() { } void* Alloc(size_t size, size_t *actual_size, size_t alignment); }; class MmapSysAllocator : public SysAllocator { public: MmapSysAllocator() : SysAllocator() { } void* Alloc(size_t size, size_t *actual_size, size_t alignment); }; class DefaultSysAllocator : public SysAllocator { public: DefaultSysAllocator() : SysAllocator() { for (int i = 0; i < kMaxAllocators; i++) { failed_[i] = true; allocs_[i] = NULL; names_[i] = NULL; } } void SetChildAllocator(SysAllocator* alloc, unsigned int index, const char* name) { if (index < kMaxAllocators && alloc != NULL) { allocs_[index] = alloc; failed_[index] = false; names_[index] = name; } } void* Alloc(size_t size, size_t *actual_size, size_t alignment); private: static const int kMaxAllocators = 2; bool failed_[kMaxAllocators]; SysAllocator* allocs_[kMaxAllocators]; const char* names_[kMaxAllocators]; }; static char sbrk_space[sizeof(SbrkSysAllocator)]; static char mmap_space[sizeof(MmapSysAllocator)]; static char default_space[sizeof(DefaultSysAllocator)]; |
在 libtcmalloc 初始化的时候,使用 new 操作符的 placeholder 形式,分别在 sbrk_space, mmap_space, default_space 上面初始化这三个 Allocator。然后调用 DefaultSysAllocator 的 SetChildAllocator 接口,将 SbrkSysAllocator 和 MmapSysAllocator 设置成待用分配器:
1 2 3 4 5 6 7 8 9 10 11 12 13 | void InitSystemAllocators(void) { MmapSysAllocator *mmap = new (mmap_space) MmapSysAllocator(); SbrkSysAllocator *sbrk = new (sbrk_space) SbrkSysAllocator(); DefaultSysAllocator *sdef = new (default_space) DefaultSysAllocator(); if (kDebugMode && sizeof(void*) > 4) { sdef->SetChildAllocator(mmap, 0, mmap_name); sdef->SetChildAllocator(sbrk, 1, sbrk_name); } else { sdef->SetChildAllocator(sbrk, 0, sbrk_name); sdef->SetChildAllocator(mmap, 1, mmap_name); } sys_alloc = tc_get_sysalloc_override(sdef); } |
DefaultSysAllocator 的 Alloc 接口是这样实现的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | void* DefaultSysAllocator::Alloc(size_t size, size_t *actual_size, size_t alignment) { for (int i = 0; i < kMaxAllocators; i++) { if (!failed_[i] && allocs_[i] != NULL) { void* result = allocs_[i]->Alloc(size, actual_size, alignment); if (result != NULL) { return result; } failed_[i] = true; } } // After both failed, reset "failed_" to false so that a single failed // allocation won't make the allocator never work again. for (int i = 0; i < kMaxAllocators; i++) { failed_[i] = false; } return NULL; } |
先后尝试使用 SbrkSysAllocator 和 MmapSysAllocator 分配内存,如果某个分配器分配失败,则标记相应的 failed_ 域,下次便不从这个分配器分配内存。如果全部失败,则返回 NULL,同时清除 failed_ 域,以免无法恢复。TCMalloc_SystemAlloc 函数分配内存时候,调用全局的 sys_alloc->Alloc() 即可。
关于全局内存的分配要介绍的就这么多,至于 SbrkSysAllocator 和 MmapSysAllocator 的 Alloc 实现细节,但凡对 sbrk 和 mmap 了解的人一猜就知道了。
下面介绍下内存的释放。可以注意到,前面的 Allocator 只有 Alloc 接口,并没有一个 Release 或者 Free 接口,那么内存是怎么释放的呢?事实上,TCMalloc 从来不释放内存!
那么,TCMalloc_SystemRelease 做些什么呢?我们知道,无论是 brk,还是 mmap,申请的都是虚拟内存,物理内存是由内核在缺页中断时按需分配的。TCMalloc_SystemRelease 并没有调用 sbrk 和 munmap 将虚拟内存『归还』给操作系统,而是调用 madvise 系统调用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | #if !defined(MADV_FREE) && defined(MADV_DONTNEED) # define MADV_FREE MADV_DONTNEED #endif bool TCMalloc_SystemRelease(void* start, size_t length) { #ifdef MADV_FREE const size_t pagemask = pagesize - 1; size_t new_start = reinterpret_cast<size_t>(start); size_t end = new_start + length; size_t new_end = end; // Round up the starting address and round down the ending address // to be page aligned: new_start = (new_start + pagesize - 1) & ~pagemask; new_end = new_end & ~pagemask; if (new_end > new_start) { int result; do { result = madvise(reinterpret_cast<char*>(new_start), new_end - new_start, MADV_FREE); } while (result == -1 && errno == EAGAIN); return result != -1; } #endif return false; } |
因为 Linux 不支持 MADV_FREE,所以使用了 MADV_DONTNEED。使用 MADV_DONTNEED 调用 madvise,告诉内核这段内存今后『很可能』用不到了,其映射的物理内存尽管拿去好了!
因此,TCMalloc_SystemRelease 只是告诉内核,物理内存可以回收以做它用,但虚拟空间还留着。
那么,是不是 TCMalloc 永远不会归还虚拟空间呢?一般来说,是的。但当虚拟空间耗尽,或者虚拟空间有碎片,无法满足内存需要时,TCMalloc 会试图合并,然后把所有空闲的虚拟空间归还,然后从新分配。