详解Go中内存分配 (3)

runtime.mcentral

type mcentral struct { lock mutex //spanClass Id spanclass spanClass // 空闲的span列表 nonempty mSpanList // list of spans with a free object, ie a nonempty free list // 已经被使用的span列表 empty mSpanList // list of spans with no free objects (or cached in an mcache) //分配mspan的累积计数 nmalloc uint64 } type mSpanList struct { //链表头 first *mspan // first span in list, or nil if none //链表尾部 last *mspan // last span in list, or nil if none }

当runtime.mcache中空间不足的时候,会去runtime.mcentral中申请对应规格的mspan。由于由于runtime.mcentral是公共资源,会有多个runtime.mcache向它申请runtime.mspan,因此必须加锁。

在runtime.mcentral中,有spanclass标识,spanclass表示这个mcentral的类型,下面我们会看到,在分配[16B,32KB]大小对象的时候,会将对象的大小分成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}

所以runtime.mcentral只负责一种spanclass规格类型,该规格的所有未被使用的空闲mspan会挂载到nonempty 链表上,已经被mcache拿走,未归还的会挂载到empty 链表上,归还后会再挂载到nonempty上。mspan会以链表的形式链接在runtime.mcentral上面。

mcentral

runtime.mheap

type mheap struct { lock mutex pages pageAlloc // page allocation data structure //arenas数组集合,一个二维数组 arenas [1 << arenaL1Bits]*[1 << arenaL2Bits]*heapArena //各个规格的mcentral集合 central [numSpanClasses]struct { mcentral mcentral pad [cpu.CacheLinePadSize - unsafe.Sizeof(mcentral{})%cpu.CacheLinePadSize]byte } ... }

对于runtime.mheap需要关注central和arenas。central是各个规格的mcentral集合,在初始化的时候会通过遍历class_to_size来进行创建;arenas是一个二维数组,用来管理内存空间。arenas由多个runtime.heapArena组成,每个单元都会管理 64MB 的内存空间:

const ( pageSize = 8192 // 8KB heapArenaBytes = 67108864 // 64MB pagesPerArena = heapArenaBytes / pageSize // 8192 ) type heapArena struct { bitmap [heapArenaBitmapBytes]byte spans [pagesPerArena]*mspan pageInUse [pagesPerArena / 8]uint8 pageMarks [pagesPerArena / 8]uint8 zeroedBase uintptr }

需要注意的是,上面的heapArenaBytes代表的64M只是在除windows以外的64 位机器才会显示,在windows机器上显示的是4MB。具体的可以看下面的官方注释:

// Platform Addr bits Arena size L1 entries L2 entries // -------------- --------- ---------- ---------- ----------- // */64-bit 48 64MB 1 4M (32MB) // windows/64-bit 48 4MB 64 1M (8MB) // */32-bit 32 4MB 1 1024 (4KB) // */mips(le) 31 4MB 1 512 (2KB)

L1 entries、L2 entries分别代表的是runtime.mheap中arenas一维、二维的值。

mheap

给对象分配内存

我们通过对源码的反编译可以知道,堆上所有的对象都会通过调用runtime.newobject函数分配内存,该函数会调用runtime.mallocgc:

//创建一个新的对象 func newobject(typ *_type) unsafe.Pointer { //size表示该对象的大小 return mallocgc(typ.size, typ, true) } func mallocgc(size uintptr, typ *_type, needzero bool) unsafe.Pointer { ... dataSize := size // 获取mcache,用于处理微对象和小对象的分配 c := gomcache() var x unsafe.Pointer // 表示对象是否包含指针,true表示对象里没有指针 noscan := typ == nil || typ.ptrdata == 0 // maxSmallSize=32768 32k if size <= maxSmallSize { // maxTinySize= 16 bytes if noscan && size < maxTinySize { ... } else { ... } // 大于 32 Kb 的内存分配,通过 mheap 分配 } else { ... } ... return x }

通过mallocgc的代码可以知道,mallocgc在分配内存的时候,会按照对象的大小分为3档来进行分配:

小于16bytes的小对象;

在16bytes与32k之间的微对象;

大于 32 Kb的大对象;

大对象分配 func mallocgc(size uintptr, typ *_type, needzero bool) unsafe.Pointer { ... var s *mspan shouldhelpgc = true systemstack(func() { s = largeAlloc(size, needzero, noscan) }) s.freeindex = 1 s.allocCount = 1 x = unsafe.Pointer(s.base()) size = s.elemsize ... return x }

从上面我们可以看到分配大于32KB的空间时,直接使用largeAlloc来分配一个mspan。

func largeAlloc(size uintptr, needzero bool, noscan bool) *mspan { // _PageSize=8k,也就是表明对象太大,溢出 if size+_PageSize < size { throw("out of memory") } // _PageShift==13,计算需要分配的页数 npages := size >> _PageShift // 如果不是整数,多出来一些,需要加1 if size&_PageMask != 0 { npages++ } ... // 从堆上分配 s := mheap_.alloc(npages, makeSpanClass(0, noscan), needzero) if s == nil { throw("out of memory") } ... return s }

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wppyzy.html