内存布局结构图
从上述结构图来看,内存分配器还是有一点小复杂的,但根据具体的逻辑层次可以拆分成三大模块--cache,central,heap,然后一个一个的模块分析下去,逻辑就显得特别清晰明了了。位于结构图最下边的Cache就是cache模块部分;central模块对应深蓝色部分的MCentral,central模块的逻辑结构很简单,所以结构图就没有详细的绘制了;Heap是结构图中的核心结构,对应heap模块,也可以看出来central是直接被Heap管理起来的,属于Heap的子模块。
在分析内存分配器这部分源码的时候,首先需要明确的是所有内存分配的入口,有了入口就可以从这里作为起点一条线的看下去,不会有太大的障碍。这个入口就是malloc.goc源文件中的runtime·mallocgc函数,这个入口函数的主要工作就是分配内存以及触发gc(本文将只介绍内存分配),在进入真正的分配内存之前,此入口函数还会判断请求的是小内存分配还是大内存分配(32k作为分界线);小内存分配将调用runtime·MCache_Alloc函数从Cache获取,而大内存分配调用runtime·MHeap_Alloc直接从Heap获取。入口函数过后,就会真正的进入到具体的内存分配过程中去了。
在真正进入内存分配过程之前,还需要了解一下整个内存分配器是如何创建的以及初始化成什么样子。完成内存分配器创建初始化的函数是runtime·mallocinit,看一下简化的源码:
void runtime·mallocinit(void)
{
// 创建mheap对象,这个是直接从操作系统分配内存。heap是全局的,所有线程共享,
一个Go进程里只有一个heap。
if((runtime·mheap = runtime·SysAlloc(sizeof(*runtime·mheap))) == nil)
runtime·throw("runtime: cannot allocate heap metadata");
// 64位平台,申请一大块内存地址保留区,后续所有page的申请都会从这个地址区里分
配。这个区就是结构图中的arena。
if(sizeof(void*) == 8 && (limit == 0 || limit > (1<<30))) {
arena_size = MaxMem;
bitmap_size = arena_size / (sizeof(void*)*8/4);
p = runtime·SysReserve((void*)(0x00c0ULL<<32), bitmap_size + arena_size);
}
// 初始化好heap的arena以及bitmap。
runtime·mheap->bitmap = p;
runtime·mheap->arena_start = p + bitmap_size;
runtime·mheap->arena_used = runtime·mheap->arena_start;
runtime·mheap->arena_end = runtime·mheap->arena_start + arena_size;