详解Go中内存分配 (5)

sysAlloc方法会调用runtime.linearAlloc.alloc预先保留的内存中申请一块可以使用的空间;如果没有会调用sysReserve方法会从操作系统中申请内存;最后初始化一个heapArena来管理刚刚申请的内存,然后将创建heapArena放入到arenas列表中。

至此,大对象的分配流程至此结束。

小对象分配

对于介于16bytes~32K的对象分配如下:

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 { var sizeclass uint8 //计算 sizeclass // smallSizeMax=1024 if size <= smallSizeMax-8 { // smallSizeDiv=8 sizeclass = size_to_class8[(size+smallSizeDiv-1)/smallSizeDiv] } else { // largeSizeDiv=128,smallSizeMax = 1024 sizeclass = size_to_class128[(size-smallSizeMax+largeSizeDiv-1)/largeSizeDiv] } size = uintptr(class_to_size[sizeclass]) spc := makeSpanClass(sizeclass, noscan) span := c.alloc[spc] //从对应的 span 里面分配一个 object v := nextFreeFast(span) if v == 0 { // mcache不够用了,则从 mcentral 申请内存到 mcache v, span, shouldhelpgc = c.nextFree(spc) } x = unsafe.Pointer(v) if needzero && span.needzero != 0 { memclrNoHeapPointers(unsafe.Pointer(v), size) } } ... } ... return x }

首先会先计算sizeclass 大小,计算 sizeclass 是通过预先定义两个数组:size_to_class8 和 size_to_class128。小于 1024 - 8 = 1016 (smallSizeMax=1024),使用 size_to_class8,否则使用数组 size_to_class128。

举个例子,比如要分配 20 byte 的内存,那么sizeclass = size_to_calss8[(20+7)/8] = size_to_class8[3] = 3。然后通过class_to_size[3]获取到对应的值32,表示应该要分配32bytes的内存值。

接着会从alloc数组中获取一个span的指针,通过调用nextFreeFast尝试从mcache中获取内存,如果mcache不够用了,则尝试调用nextFree从 mcentral 申请内存到 mcache。

下面看看nextFreeFast:

func nextFreeFast(s *mspan) gclinkptr { // 获取allocCache二进制中0的个数 theBit := sys.Ctz64(s.allocCache) // Is there a free object in the allocCache? if theBit < 64 { result := s.freeindex + uintptr(theBit) if result < s.nelems { freeidx := result + 1 if freeidx%64 == 0 && freeidx != s.nelems { return 0 } s.allocCache >>= uint(theBit + 1) s.freeindex = freeidx s.allocCount++ return gclinkptr(result*s.elemsize + s.base()) } } return 0 }

allocCache在初始化的时候会初始化成^uint64(0),换算成二进制,如果为0则表示被占用,通过allocCache可以快速的定位待分配的空间:

allocCache

func (c *mcache) nextFree(spc spanClass) (v gclinkptr, s *mspan, shouldhelpgc bool) { s = c.alloc[spc] shouldhelpgc = false // 当前span中找到合适的index索引 freeIndex := s.nextFreeIndex() // 当前span已经满了 if freeIndex == s.nelems { if uintptr(s.allocCount) != s.nelems { println("runtime: s.allocCount=", s.allocCount, "s.nelems=", s.nelems) throw("s.allocCount != s.nelems && freeIndex == s.nelems") } // 从 mcentral 中获取可用的span,并替换掉当前 mcache里面的span c.refill(spc) shouldhelpgc = true s = c.alloc[spc] // 再次到新的span里面查找合适的index freeIndex = s.nextFreeIndex() } if freeIndex >= s.nelems { throw("freeIndex is not valid") } // 计算出来内存地址,并更新span的属性 v = gclinkptr(freeIndex*s.elemsize + s.base()) s.allocCount++ if uintptr(s.allocCount) > s.nelems { println("s.allocCount=", s.allocCount, "s.nelems=", s.nelems) throw("s.allocCount > s.nelems") } return }

nextFree中会判断当前span是不是已经满了,如果满了就调用refill方法从 mcentral 中获取可用的span,并替换掉当前 mcache里面的span。

func (c *mcache) refill(spc spanClass) { s := c.alloc[spc] ... s = mheap_.central[spc].mcentral.cacheSpan() if s == nil { throw("out of memory") } ... c.alloc[spc] = s }

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

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