相关概念
整体结构图:

page
一个page大小为8kb,page是go语言内存管理和虚拟内存交互时的最小单元。
mspan
表示一组连续的page(整数倍的page)
size class相关
-
object size: 指协程应用逻辑一次向go语言内存申请的对象object大小。
-
object: go语言内存管理模块针对内存管理更加细化的内存管理单元。 -
span: span在初始化时会被分为多个object。
-
size class: 表示一块内存所属的规格或者刻度,例如object size在1-8B的object属于size class1级别,8-16B大小的为size class2级别。(go语言划分了66个size)
-
span class: 针对span进行划分,是span大小的级别。一个size class会对应两个span class,其中一个span存放需要gc扫描的对象(包含指针的对象),另一个span存放不需要gc扫描的对象(不含指针的)
Demo
object size大小为8B大小的object,所属的span的大小为8kb,那么这个span就会被平均分为1024个object。
相关图解:

MCache
简介
MCache与go语言的GMP模型中的 P进行绑定 ,因为真正可运行的线程M的数量与P一致,所以与P绑定更能 节省内存空间,可以保证每个 G 使用MCache时 不需要加锁 就可以获取内存。(因为一个P只有一个M在其上运行,不可能出现竞争,所以没有锁限制,进而加快了内存的分配)
内部构造
如图所示:

MCache中每个 span class 会对应一个 Mspan ,不同span class的mspan的总体长度不同(参考runtime/sizeclass.go)。当其中某个span class对应的mspan已经没有可以提供的object时,MCache会向MCentral申请一个对应的mspan。
特殊size class
对于span class为0和1,也就是 size class为0 的规格刻度内存,MCache实际上没有分配任何内存。Go语言内存管理对内存为0的数据申请做了特殊处理,如果申请的数据大小为0,则将直接返回一个 固定内存地址 ,不会进入Go语言内存管理的正常逻辑。
这也是为什么在做channel同步时,会发送一个struct{}数据,因为不会申请任何内存,能够适当节省一部分内存空间 。
MCentral
简介
当MCache中的某个size class对应的span被一次次的object上层取走后,如果出现当前size class的span空缺的情况,MCache会向MCentral申请对应的span。申请时需要 加锁 。

内部构造
MCentral针对每个span class 级别有两个 span链表 。

MCentral是一个抽象的概念,实际上每个span class对应的内存数据结构是一个MCentral, 即在MCentral这层数据管理中,实际上有span class个MCentral小内存管理单元。
内部字段:
|
|
partial 和 full 都是一个spanSet数组类型,都各有两个spanSet,这是为了给 GC 使用的,其中一个是已扫描的,另一个是未扫描的。
MHeap
简介
MHeap是内存块的管理对象,通过page对内存单元进行管理。用来详细管理每一系列page的结构称为一个 HeapArena 。一个HeapArena占用内存 64MB ,其中里面的内存是一个个的mspan,最小单元依然是pages。所有的HeapArena组成的集合是一个 Arenas ,即MHeap针对堆内存的管理。MHeap上游是MCentral,当MCentral中的span不够时向MHeap申请。MHeap的下游是操作系统,当MHeap内存不足时会向操作系统的 虚拟内存 空间申请,访问MHeap获取内存时依然需要 加锁。
MHeap中HeapArena占用了绝大部分的空间,其中每个HeapArena包含一个 bitmap ,其作用是标记当前这个HeapArena的内存使用情况。其主要服务于 GC 垃圾回收模块,bitmap共有 两种标记,一种是标记对应地址中 是否存在对象,另一种是标记此对象 是否被GC模块标记过,所以当前HeapArena中的所有page均会被bitmap标记。
内部构造
如图所示:

对象分配流程
go语言内存管理中的对象划分:
| 对象级别 | 大小范围 |
|---|---|
| 微对象(Tiny对象) | [1,16B) |
| 小对象 | [16B,32KB] |
| 大对象 | (32KB,无限大) |
Tiny对象分配流程
针对Tiny对象,go语言做了特殊处理,MCache中不仅保存着各个span class级别的内存块空间,还有一个比较特殊的 Tiny存储空间。
Tiny空间是从size class=2(对应span class=4\5)中获取的一个16B的object,作为Tiny对象的分配空间。
主要是因为类似bool或者1字节的byte,也都会独享8B的内存空间,进而导致一定的空间浪费。所以将申请的object小于16B的申请同意归为Tiny对象申请。
分配过程:
-
P向MCache申请微小对象,如一个bool变量。如果申请的object在Tiny对象的大小范围,则进入Tiny对象的申请流程,否则进入小对象或者大对象的申请流程。 -
判断申请的tiny对象是否包含
指针,如果包含指针,则进入小对象的申请流程(不会放在tiny缓冲区,因为需要GC进入扫描流程)。 -
如果tiny空间的16B没有多余的存储容量,则从size class=2的span中获取一个16B的object放入tiny缓冲区中。
-
将1B的bool类型放置在16B的tiny空间中,以
字节对齐的方式放置。(tiny对象的申请也达不到内存利用率100%)(此处不做详细内存对齐讲解)
小对象
分配小对象的流程是按照span class的规格匹配的。
分配过程:
-
协程逻辑层
P向go语言内存管理申请一个对象所需的内存空间。 -
MCache收到请求后,根据对象所需的内存空间计算出需要的size。 -
判断size是否小于16B,小于进入tiny对象的申请流程,否则进入小对象的申请流程。
-
根据size匹配对应的
size class内存规格,在根据size class和该对象是否包含指针,来定位是从noscan span class还是从scan span class获取空间,如果没有指针,则锁定noscan。 -
在定位的span class中的span取出一个object返回给协程逻辑层P。流程结束
-
如果定位的span class中的span所有的内存块object都被占用,则
MCache会向MCentral申请一个span。 -
MCentral收到内存申请后,优先从对应的span class中的
Partial Set里取出span,如果Partial Set里没有,则从Full Set中取,返给MCache。 -
MCache得到span后,补充 到对应的span class中,之后再次执行第5步。
-
如果Full Set中没有符合的span,则MCentral会向
MHeap申请内存。 -
MHeap收到请求后从其中一个
HeapArena中取出一部分pages返给MCentral,当MHeap没有足够空间时向操作系统申请内存,将申请的内存也保存到HeapArena中的mspan中。MCentral将从MHeap获取的由pages组成的span添加到对应的span class集合中作为补充,之后继续执行第7步。 -
最后协程业务逻辑层得到对象申请到的内存,流程结束。
大对象
小对象从MCache中分配,而大对象直接从MHeap中分配。对于不满足MCache分配范围的对象均按照大对象处理。
分配过程:
-
协程逻辑层申请大对象所需的内存空间,如果超过32KB,则直接
绕过MCache和MCentral向MHeap申请。 -
MHeap根据对象所需的空间计算得到需要多少个pages。
-
MHeap向
Arenas中的HeapArena申请对应的pages。 -
如果Arenas中没有HeapArena可提供合适的pages内存,则向操作系统的虚拟内存申请,并且填充到Arenas中。
-
MHeap返回大对象的内存空间,协程逻辑层得到内存,流程结束