Go学习笔记(day7) | 青训营笔记

106 阅读4分钟

这是我参与「第五届青训营」伴学笔记创作活动的第7天

笨人纯小白,笔记包括一些上课学到的知识和课外总结的内容,如有错误请指正!

十、Golang内存管理(二:堆内存)

在64位机器上,Go程序启动时,首先会向操作系统申请一块大小为64M的虚拟内存单元,也叫heapArena

最多可以有2的22次方个内存单元heapArena,所有的heapArena组成了Go的堆内存。

假设物理内存为64G,操作系统给每个进程分配了256T的虚拟内存。

image.png

10.1 分级分配

heapArena是Go程序每次申请虚拟内存的单位,这些申请的虚拟内存单位就是Go的堆内存,堆内存由mheap管理。

为了避免出现内存碎片的情况,Go采取了分级分配的策略,根据对象的大小进行内存分配。mheap将这些申请的内存单元heapArena根据对象的大小切分成多个不同规格的小内存块,这些不同规格的内存块称为mspan

10.2 mspan

mspan被划分为67种,代表着67种内置不同大小的内存块的mspan,每种mspan为N个相同大小的内存块class,可以满足各种对象的内存分配,而且每种mspan的大小也可能不相同。其中四种mspan示意图如下: image.png

image.png

  • class:代表每种mspan编号。
  • bytes/obj:每种mspan中内存块的大小,可以理解为上图中每个格子的大小。
  • byte/span:每种mspan的大小,占用堆的字节数
  • objects:每种mspan中包含多少个小内存块,可以理解为上图中每个mspan的格子数量。 完整的class表可以在runtime包下的sizeclasses.go中查看。

mspan数据结构:

    type mspan struct {
        next *mspan                 // next span in list, or nil if none
        prev *mspan                 // previous span in list, or nil if none
        startAddr uintptr           // 起始地址
        npages    uintptr           // span中包含的页数
        nelems uintptr              // 上图span中的class数量,代表可分配的内存块数量
        allocCount  uint16          // 已分配的内存块(格子)个数
        spanclass   spanClass       // span中内存块的规格
        elemsize    uintptr         // span中每一个内存块大小
        allocBits  *gcBits          // 分配位图,代表mspan中每一个内存块的分配情况
        gcmarkBits *gcBits          // mspan中每一个内存块的标记情况
        ......
    }

10.3 mcentral

为了管理这么多的mspan,于是有了mcentral;每个mcentral用于管理一种特定规格的mspan。

mcentral一共有136种。按理说mcentral是管理mspan的,mspan有67种,为什么mcentral却有136种呢?

其实mspan又分为需要gc扫描和不需要gc扫描的,mspan还有一个class0,用于大对象分配。

mcentral数据结构:

    type mcentral struct {
        spanclass spanClass
        partial [2]spanSet // list of spans with a free object,空闲块span链表
        full    [2]spanSet // list of spans with no free objects,没有空闲块的span链表
    }

mcentral数据结构中并没有锁,协程申请内存时需要向mcentral申请mpsan,此时会调用cacheSpan() *mspan,返回值是*mspan,申请到的mspan会加入到另一个结构中供协程使用,而调用cacheSpan() *mspan的过程中是需要加锁的,在高并发的场景下,多个协程并发的申请锁,锁的频繁加锁解锁的开销非常大,因此就需要刚才说的另一个结构来缓冲这种开销压力。

10.4 mcache

mcache相当于一个协程的本地队列,里面存储着从mcentral申请的mspan,协程向mcentral申请mspan需要加锁,为了避免多个协程申请内存不断加锁,于是引入了mcache,这个思想就是参考了GMP模型的本地协程队列。

mcentral是全局资源,为多个协程服务,当协程内存不足时会向mcentral申请。

mcache数据结构:

    type mcache struct {
        ...
            alloc [numSpanClasses]*mspan // 保存着申请到的mpsan, numSpanClasses = 68 << 1
        ...
    }

mcache在初始化时是没有任何mspan的,在使用过程中会动态的从mcentral中申请并保存至alloc 中,这样协程需要申请内存时,就直接从自己的本地缓存中获取mspan,避免了加锁。

image.png mcache中相同类型的mspan通过链表连接。