Featured image of post Go语言内存分配模型

Go语言内存分配模型

Go语言内存分配模型个人学习笔记

相关概念

整体结构图: 整体结构图

page

一个page大小为8kb,page是go语言内存管理和虚拟内存交互时的最小单元。

mspan

表示一组连续的page(整数倍的page)

size class相关

  1. object size: 指协程应用逻辑一次向go语言内存申请的对象object大小。

  2. object: go语言内存管理模块针对内存管理更加细化的内存管理单元。

  3. span: span在初始化时会被分为多个object。

  4. size class: 表示一块内存所属的规格或者刻度,例如object size在1-8B的object属于size class1级别,8-16B大小的为size class2级别。(go语言划分了66个size)

  5. span class: 针对span进行划分,是span大小的级别。一个size class会对应两个span class,其中一个span存放需要gc扫描的对象(包含指针的对象),另一个span存放不需要gc扫描的对象(不含指针的)

Demo

object size大小为8B大小的object,所属的span的大小为8kb,那么这个span就会被平均分为1024个object。

相关图解: Object size 与 Span的关系 Span Class 与 Size Class 的逻辑关系

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小内存管理单元。

内部字段:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
type mcentral struct {
	_         sys.NotInHeap
	spanclass spanClass
    //当前mcantral对应的spanclass等级
    
	partial [2]spanSet // list of spans with a free object
    //表示还有可用空间的集合,集合中的所有span都至少有一个空闲的object空间。 
    //如果MCentral上游的MCache退还span,则会将退还的span加入集合
    
	full    [2]spanSet // list of spans with no free objects
    //表示没有可用空间的span链表,该链表上的span都不确定是否还有空闲的object空间。
    //如果MCentral将一个span提供给上游MCache,则被提供的span就会加入集合

}

partialfull 都是一个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标记。

内部构造

如图所示: MHeap内部构造

对象分配流程

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对象申请。

分配过程:

  1. PMCache 申请微小对象,如一个bool变量。如果申请的object在Tiny对象的大小范围,则进入Tiny对象的申请流程,否则进入小对象或者大对象的申请流程。

  2. 判断申请的tiny对象是否包含 指针 ,如果包含指针,则进入 小对象 的申请流程(不会放在tiny缓冲区,因为需要 GC 进入扫描流程)。

  3. 如果tiny空间的16B没有多余的存储容量,则从size class=2的span中获取一个16B的object放入tiny缓冲区中。

  4. 将1B的bool类型放置在16B的tiny空间中,以 字节对齐 的方式放置。(tiny对象的申请也达不到内存利用率100%)(此处不做详细内存对齐讲解)

小对象

分配小对象的流程是按照span class的规格匹配的。

分配过程:

  1. 协程逻辑层 P 向go语言内存管理申请一个对象所需的内存空间。

  2. MCache 收到请求后,根据对象所需的内存空间计算出需要的size。

  3. 判断size是否小于16B,小于进入tiny对象的申请流程,否则进入小对象的申请流程。

  4. 根据size匹配对应的 size class 内存规格,在根据size class和该对象是否包含 指针,来定位是从noscan span class还是从scan span class获取空间,如果没有指针,则锁定 noscan

  5. 在定位的span class中的span取出一个object返回给协程逻辑层P。流程结束

  6. 如果定位的span class中的span所有的内存块object都被占用,则 MCache 会向 MCentral 申请一个span。

  7. MCentral收到内存申请后,优先从对应的span class中的 Partial Set 里取出span,如果Partial Set里没有,则从 Full Set 中取,返给MCache。

  8. MCache得到span后,补充 到对应的span class中,之后再次执行第5步。

  9. 如果Full Set中没有符合的span,则MCentral会向 MHeap 申请内存。

  10. MHeap收到请求后从其中一个 HeapArena 中取出一部分pages返给MCentral,当MHeap没有足够空间时向 操作系统 申请内存,将申请的内存也保存到HeapArena中的mspan中。MCentral将从MHeap获取的由pages组成的span添加到对应的span class 集合中作为补充 ,之后继续执行第7步。

  11. 最后协程业务逻辑层得到对象申请到的内存,流程结束。

大对象

小对象从MCache中分配,而大对象直接从MHeap中分配。对于不满足MCache分配范围的对象均按照大对象处理。

分配过程:

  1. 协程逻辑层申请大对象所需的内存空间,如果超过32KB,则直接 绕过MCacheMCentralMHeap 申请。

  2. MHeap根据对象所需的空间计算得到需要多少个pages。

  3. MHeap向 Arenas 中的HeapArena申请对应的pages。

  4. 如果Arenas中没有HeapArena可提供合适的pages内存,则向操作系统的虚拟内存申请,并且填充到Arenas中。

  5. MHeap返回大对象的内存空间,协程逻辑层得到内存,流程结束

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计