这是一个系列的Go语言知识介绍文章,你可以关注

Golang提供内存管理功能,开发者不需要关系内存的申请和释放,这样为使用者带来极大的便利。

内存管理模型

Golang提供两级内存管理机制,第一级为p级内存管理mcache,第二级为全局内存管理mheap。

在为对象申请内存时,采用递进式内存申请如下图所示,当某一级对象不够用,则向后一级申请。

内存申请

mcache

每个p都有一个独立的mcache,为该p单独提供内存管理。这样就可以降低p间的竞争,提高内存并发申请效率。mcache主要维护2个对象:

tiny:为长度小于16字节且非扫描(不含指针)对象分配内存。tiny本身为16字节长度的内存块,在分配内存时,更新已经分配内存的偏移量。

alloc:保存不同大小规格的mspan对象(下文具体介绍),每种规格只有一个mspan对象,为长度小于32768字节的对象分配内存。其中tiny也是从alloc[1]的mspan中申请的object,对于大于16字节而小于32768字节的对象则直接从对应规格的alloc中的mspan中申请object。

当mcache中的mspan无可用的空间,则向mheap申请新的mspan。

mheap

全局仅有一个mheap,mheap管理如下对象:

pages:所有申请的内存页。Go申请内存的时,都是按照页(page)申请,目的是减少系统内存碎片。

mcentral:维护不同大小规格的满/部分空闲的mspan链。当mcache中的mspan使用完(即mspan中无可用object),则放入对应尺寸的mcentral的full-mspan链中,同时从部分满partial-mspan链中获取一个mspan对象。

arenas:提供地址到mspan的映射,主要用于gc快速查找地址对应的object。

allspans:所有申请的mspan链。

mspan

mspan由1个或者连续多个内存页组成,内部划分为若干个object。

mspan结构

根据object的大小分为67个规格,如下:

从上面的规格来看,没有规格0,实际上规格0用来指示所有大对象(大于32768字节)span。以规格4解释每一个指标的由来:

bytes/obj:规格4每个object为32字节。

bytes/span:一个span的内存大小为8192字节(即一个内存页大小),因为一个内存页足够分配32字节的内存空间。

objects:一个span下objects个数为256=8192/32。

tail waste:span连续分配object后,不足分配1个object的剩余空间大小。因为8192字节的内存能完成分配256个32字节的object,不存在剩余空间,所以tail waste为0。

max waste:申请规格4的对象最小内存长度为25字节(小于25字节则申请3及以下规格mspan),如果每个object都被25字节的对象申请,此时内存浪费最大,对应浪费率为(32-25)/32=21.875%。

mspan有如下关键属性:

例如在某个时刻,mspan的起始部分使用情况如下图,底色为浅蓝色表示该对象已经被申请,底色为白色则表示该对象处于未被占用状态。

mspan占用位图

则allocbits二进制值为:

如上图中每格中的数值,1表示该对象已经被申请,0则表示该对象空闲。

而allocCache表示以freeindex为起始,最近64个节点(即[freeindex, freeindex+63]节点)的对象申请情况,其每个bit位的值为allocbits值取反。如上图,如果freeindex为0,则allocCache的二进制值为:

allocCache用于快速找到最近空闲对象节点。当从mspan中申请一个object时,则通过找到allocCache第一个非零bit位偏移位数,记为bit1,则freeindex+bit1则为最近空闲object节点,如上图如果freeindex为0,则节点1为最近空闲节点。

gcmarkBits在启动gc的时候会初始化为全0,在GC标记阶段,如果该对象在被设置成1。当GC结束后,allocbits将被赋值成gcmarkBits(刚好1在gcmarkBits表示黑色,对应allocbits被申请状态),作为allocbits的初始值。这里需要注意的是,在申请mspan对象过程中,并不修改allocbits的值。

arena

arena主要用于协助垃圾回收快速查找内存地址指向对象的信息。在x86-amd64平台下,Golang将进程的内存空间(除去非堆空间)按照64M(2的26次方)字节大小划分成若干块。Golang认为进程最大堆内存空间为256T(2的48次方)字节,则整个内存空间可以分为256T/64M=4M(2的22次方)块。每一块内存由一个arena对象维护其相关属性。

arena分布

arena主要包含如下两个属性:

其中:

bitmap记录整个arena下内存地址指向的对象所包含的对象指针应用关系,大小为64M/(8*8/2)=2M。

一个arena对应的地址为64M,每个指针大小为8字节,bitmap为byte(长度为8)数组,每个字节只有一半用来表示指针,所以bitmap大小为64M/(8*8/2)=2M。

bitmap每个字节分为高低4bit两部分,高4bit的每个bit表示指向的地址其后是否存在指针对象,1表示存在,0表示不存在。低4bit每个bit表示指向的地址是否为指针,1表示指针,0表示非指针(标量)。

arena指针引用位图

spans以内存页为粒度,记录arena下每个内存页所属的mspan。之所以以内存页大小为单元,是因为每个mspan指向的内存大小为内存页的整数倍,所以每个内存页只可能归属于一个mspan。

一个arena对应的地址为64M,一个内存页的大小为8K,所以spans大小为64M/8K=8K。
arena与mspan映射关系

在GC标记过程中,由对象指针,通过arena的bitmap可以快速扫描该对象下指针引用,并通过spans快速找到对象所属mspan,并标记其对应的gcmarkBits(实现过程在垃圾回收章节详细描述)