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:
- 计算对象所需内存大小size
- 根据size到size class映射,计算出所需的size class
- 根据size class和对象是否包含指针计算出span class
- 获取该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