Refill 根据指定的sizeclass获取对应的span,并作为 mcache的新的sizeclass对应的span。
func (c *mcentral) cacheSpan() *mspan { ... sg := mheap_.sweepgen spanBudget := 100 var s *mspan // 从清理过的、包含空闲空间的spanSet结构中查找可以使用的内存管理单元 if s = c.partialSwept(sg).pop(); s != nil { goto havespan } for ; spanBudget >= 0; spanBudget-- { // 从未被清理过的、有空闲对象的spanSet查找可用的span s = c.partialUnswept(sg).pop() if s == nil { break } if atomic.Load(&s.sweepgen) == sg-2 && atomic.Cas(&s.sweepgen, sg-2, sg-1) { // 找到要回收的span,触发sweep进行清理 s.sweep(true) goto havespan } } for ; spanBudget >= 0; spanBudget-- { // 获取未被清理的、不包含空闲空间的spanSet查找可用的span s = c.fullUnswept(sg).pop() if s == nil { break } if atomic.Load(&s.sweepgen) == sg-2 && atomic.Cas(&s.sweepgen, sg-2, sg-1) { s.sweep(true) freeIndex := s.nextFreeIndex() if freeIndex != s.nelems { s.freeindex = freeIndex goto havespan } c.fullSwept(sg).push(s) } } // 从堆中申请新的内存管理单元 s = c.grow() if s == nil { return nil } havespan: n := int(s.nelems) - int(s.allocCount) if n == 0 || s.freeindex == s.nelems || uintptr(s.allocCount) == s.nelems { throw("span has no free objects") } //更新 nmalloc atomic.Xadd64(&c.nmalloc, int64(n)) usedBytes := uintptr(s.allocCount) * s.elemsize atomic.Xadd64(&memstats.heap_live, int64(spanBytes)-int64(usedBytes)) if trace.enabled { // heap_live changed. traceHeapAlloc() } if gcBlackenEnabled != 0 { // heap_live changed. gcController.revise() } freeByteBase := s.freeindex &^ (64 - 1) whichByte := freeByteBase / 8 // 更新allocCache s.refillAllocCache(whichByte) // s.allocCache. s.allocCache >>= s.freeindex % 64 return s }cacheSpan主要是从mcentral的spanset中去寻找可用的span,如果没找到那么调用grow方法从堆中申请新的内存管理单元。
获取到后更新nmalloc、allocCache等字段。
runtime.mcentral.grow触发扩容操作从堆中申请新的内存:
func (c *mcentral) grow() *mspan { // 获取待分配的页数 npages := uintptr(class_to_allocnpages[c.spanclass.sizeclass()]) size := uintptr(class_to_size[c.spanclass.sizeclass()]) // 获取新的span s := mheap_.alloc(npages, c.spanclass, true) if s == nil { return nil } // Use division by multiplication and shifts to quickly compute: // n := (npages << _PageShift) / size n := (npages << _PageShift) >> s.divShift * uintptr(s.divMul) >> s.divShift2 // 初始化limit s.limit = s.base() + size*n heapBitsForAddr(s.base()).initSpan(s) return s }grow里面会调用runtime.mheap.alloc方法获取span,这个方法在上面已经讲过了,不记得的同学可以翻一下文章上面。
到这里小对象的分配就讲解完毕了。
微对象分配 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 { off := c.tinyoffset // 指针内存对齐 if size&7 == 0 { off = alignUp(off, 8) } else if size&3 == 0 { off = alignUp(off, 4) } else if size&1 == 0 { off = alignUp(off, 2) } // 判断指针大小相加是否超过16 if off+size <= maxTinySize && c.tiny != 0 { // 获取tiny空闲内存的起始位置 x = unsafe.Pointer(c.tiny + off) // 重设偏移量 c.tinyoffset = off + size // 统计数量 c.local_tinyallocs++ mp.mallocing = 0 releasem(mp) return x } // 重新分配一个内存块 span := c.alloc[tinySpanClass] v := nextFreeFast(span) if v == 0 { v, _, shouldhelpgc = c.nextFree(tinySpanClass) } x = unsafe.Pointer(v) //将申请的内存块全置为 0 (*[2]uint64)(x)[0] = 0 (*[2]uint64)(x)[1] = 0 // 如果申请的内存块用不完,则将剩下的给 tiny,用 tinyoffset 记录分配了多少。 if size < c.tinyoffset || c.tiny == 0 { c.tiny = uintptr(x) c.tinyoffset = size } size = maxTinySize } ... } ... return x }在分配对象内存的时候做了一个判断, 如果该对象的大小小于16bytes,并且是不包含指针的,那么就可以看作是微对象。
在分配微对象的时候,会先判断一下tiny指向的内存块够不够用,如果tiny剩余的空间超过了size大小,那么就直接在tiny上分配内存返回;