type mspan struct {
    //链表前向指针,用于将span链接起来
    next *mspan  
    //链表前向指针,用于将span链接起来
    prev *mspan   
    // 起始地址,也即所管理页的地址
    startAddr uintptr     
    // 管理的页数
    npages uintptr 
    // 块个数,表示有多少个块可供分配
    nelems uintptr 
    //分配位图,每一位代表一个块是否已分配
    allocBits *gcBits 
    // 已分配块的个数
    allocCount uint16   
    // class表中的class ID,和Size Classs相关 Size_Classs = Span_Classs / 2
    spanclass spanClass  
    // class表中的对象大小,也即块大小
    elemsize uintptr 
}

mspan再分配的小单元的大小就是根据Size_Classs来划分的

const _NumSizeClasses = 67
var class_to_size = [_NumSizeClasses]uint16{0, 8, 16, 32, 48, 64, 80, 96, 112, 128, 144, 160, 176, 192, 208, 224, 240, 256, 288, 320, 352, 384, 416, 448, 480, 512, 576, 640, 704, 768, 896, 1024, 1152, 1280, 1408, 1536,1792, 2048, 2304, 2688, 3072, 3200, 3456, 4096, 4864, 5376, 6144, 6528, 6784, 6912, 8192, 9472, 9728, 10240, 10880, 12288, 13568, 14336, 16384, 18432, 19072, 20480, 21760, 24576, 27264, 28672, 32768}

mspan所能分到的页数也是根据Size_Classs来划分的

const _NumSizeClasses = 67
var class_to_allocnpages = [_NumSizeClasses]uint8{0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 2, 1, 2, 1, 3, 2, 3, 1, 3, 2, 3, 4, 5, 6, 1, 7, 6, 5, 4, 3, 5, 7, 2, 9, 7, 5, 8, 3, 10, 7, 4}

拿上图举例:

npages =1 标示分配了一个Page
spanclass = 10 => sizeclass = 5 => 查询一个object大小elemsize = class_to_size[10] = 144B
nelems = 8KB/144B = 56.88 = 56,有一些内存浪费掉了
allocBits就是标记分配块的分配情况。(在每个mspan管理的所有地址可能继续被分成小的单元,根据对象的大小,将这块也分为更多小的块)
mspanmspan

Golang内存分配:

mcache(每个工作线程都会绑定一个mcache),不同的线程之间不存在竞争,不需要消耗额外的锁资源。

type mcache struct {
    alloc [numSpanClasses]*mspan
}
numSpanClasses = _NumSizeClasses << 1 //span class 数量是 size class 数量的两倍
mspanmspan

size class 到 span class的计算如下:

// noscan为true代表对象不包含指针
func makeSpanClass(sizeclass uint8, noscan bool) spanClass {
       return spanClass(sizeclass<<1) | spanClass(bool2int(noscan))
}        

mcentral(管理全局的mspan供所有线程使用,存在锁竞争的问题),上图把mcentral归为一个,实际有134个mcentral,每个mcentral管理自己mspan

mcentralmcachemspancentralmspanmcentralmspanmspanobjectmcachemspanmcentral
mspan
mspanobjectcachemspanmspan
mcentral
type mcentral struct {
    // 互斥锁
    lock mutex  
    // 规格
    sizeclass int32  
    // 尚有空闲object的mspan链表
    nonempty mSpanList  
    // 没有空闲object的mspan链表,或者是已被mcache取走的msapn链表
    empty mSpanList  
    // 已累计分配的对象个数
    nmalloc uint64 
}

mheap(管理Go的所有动态分配内存)

mheapmheap_mheap
mcentralmspanmheapmheapmheapmspanmcentralmheapmcentralmcachemcentralmspanmcentralmspan

当分配一个对象的时候,为它寻找合适的mspan:

  1. 计算对象所需内存大小size
  2. 根据size到size class映射,计算出所需的size class
  3. 根据size class和对象是否包含指针计算出span class
  4. 获取该span class指向的span。

具体如下:

  1.<=16B 的对象使用mcache的tiny分配器分配;

  2.(16B,32KB] 的对象,首先计算对象的规格大小,然后使用mcache中相应规格大小的mspan分配;

    如果mcache没有相应规格大小的mspan,则向mcentral申请

    如果mcentral没有相应规格大小的mspan,则向mheap申请

    如果mheap中也没有合适大小的mspan,则向操作系统申请

  3.>32KB大对象,使用mheap直接分配,若mheap没有足够的内存,则mheap向虚拟内存申请若干个pages