tiny-allocator
图 7-20 微分配器的工作原理
如上图所示,微分配器已经在 16 字节的内存块中分配了 12 字节的对象,如果下一个待分配的对象小于 4 字节,它就会直接使用上述内存块的剩余部分,减少内存碎片,不过该内存块只有在 3 个对象都被标记为垃圾时才会被回收 。
线程缓存 runtime.mcache 中的 tiny 字段指向了 maxTinySize 大小的块,如果当前块中还包含大小合适的空闲内存,运行时会通过基地址和偏移量获取并返回这块内存:
func mallocgc(size uintptr, typ *_type, needzero bool) unsafe.Pointer { ... if size <= maxSmallSize {if noscan && size < maxTinySize {off := c.tinyoffsetif off+size <= maxTinySize && c.tiny != 0 {x = unsafe.Pointer(c.tiny + off)c.tinyoffset = off + sizec.local_tinyallocs++releasem(mp)return x}...}... } ...}
当内存块中不包含空闲的内存时,下面的这段代码会从先线程缓存找到跨度类对应的内存管理单元 runtime.mspan,调用 runtime.nextFreeFast 获取空闲的内存;当不存在空闲内存时,我们会调用 runtime.mcache.nextFree 从中心缓存或者页堆中获取可分配的内存块:
func mallocgc(size uintptr, typ *_type, needzero bool) unsafe.Pointer { ... if size <= maxSmallSize {if noscan && size < maxTinySize {...span := c.alloc[tinySpanClass]v := nextFreeFast(span)if v == 0 {v, _, _ = c.nextFree(tinySpanClass)}x = unsafe.Pointer(v)(*[2]uint64)(x)[0] = 0(*[2]uint64)(x)[1] = 0if size < c.tinyoffset || c.tiny == 0 {c.tiny = uintptr(x)c.tinyoffset = size}size = maxTinySize}... } ... return x}
获取新的空闲内存块之后,上述代码会清空空闲内存中的数据、更新构成微对象分配器的几个字段 tiny 和 tinyoffset 并返回新的空闲内存 。
小对象小对象是指大小为 16 字节到 32,768 字节的对象以及所有小于 16 字节的指针类型的对象,小对象的分配可以被分成以下的三个步骤:
- 确定分配对象的大小以及跨度类 runtime.spanClass;
- 从线程缓存、中心缓存或者堆中获取内存管理单元并从内存管理单元找到空闲的内存空间;
- 调用 runtime.memclrNoHeapPointers 清空空闲内存中的所有数据;
func mallocgc(size uintptr, typ *_type, needzero bool) unsafe.Pointer { ... if size <= maxSmallSize {...} else {var sizeclass uint8if size <= smallSizeMax-8 {sizeclass = size_to_class8[(size+smallSizeDiv-1)/smallSizeDiv]} else {sizeclass = size_to_class128[(size-smallSizeMax+largeSizeDiv-1)/largeSizeDiv]}size = uintptr(class_to_size[sizeclass])spc := makeSpanClass(sizeclass, noscan)span := c.alloc[spc]v := nextFreeFast(span)if v == 0 {v, span, _ = c.nextFree(spc)}x = unsafe.Pointer(v)if needzero && span.needzero != 0 {memclrNoHeapPointers(unsafe.Pointer(v), size)}} } else {... } ... return x}
在上述代码片段中,我们会重点分析两个函数和方法的实现原理,它们分别是 runtime.nextFreeFast 和 runtime.mcache.nextFree,这两个函数会帮助我们获取空闲的内存空间 。runtime.nextFreeFast 会利用内存管理单元中的 allocCache 字段,快速找到该字段中位 1 的位数,我们在上面介绍过 1 表示该位对应的内存空间是空闲的:func nextFreeFast(s *mspan) gclinkptr { theBit := sys.Ctz64(s.allocCache) if theBit < 64 {result := s.freeindex + uintptr(theBit)if result < s.nelems {freeidx := result + 1if freeidx%64 == 0 && freeidx != s.nelems {return 0}s.allocCache >>= uint(theBit + 1)s.freeindex = freeidxs.allocCount++return gclinkptr(result*s.elemsize + s.base())} } return 0}
找到了空闲的对象后,我们就可以更新内存管理单元的 allocCache、freeindex 等字段并返回该片内存了;如果我们没有找到空闲的内存,运行时会通过 runtime.mcache.nextFree找到新的内存管理单元:func (c *mcache) nextFree(spc spanClass) (v gclinkptr, s *mspan, shouldhelpgc bool) { s = c.alloc[spc] freeIndex := s.nextFreeIndex() if freeIndex == s.nelems {c.refill(spc)s = c.alloc[spc]freeIndex = s.nextFreeIndex() } v = gclinkptr(freeIndex*s.elemsize + s.base()) s.allocCount++ return}
在上述方法中,如果我们在线程缓存中没有找到可用的内存管理单元,会通过前面介绍的 runtime.mcache.refill 使用中心缓存中的内存管理单元替换已经不存在可用对象的结构体,该方法会调用新结构体的runtime.mspan.nextFreeIndex 获取空闲的内存并返回 。
大对象运行时对于大于 32KB 的大对象会单独处理,我们不会从线程缓存或者中心缓存中获取内存管理单元,而是直接在系统的栈中调用 runtime.largeAlloc 函数分配大片的内存:
推荐阅读
- 茶艺享受的是时间,茶艺根据不同的原则和方法的分类
- 茶油的营养价值,长期吃茶油的坏处
- 茶具的选购之配套用具,茶具的奇思妙想
- 科学管理Linux系统中的组与组成员
- 大红袍的产地,名茶大红袍的产地是哪
- svchost占用内存过高是怎么回事
- 基于MEC的边缘CDN业务调度方案及测试分析
- 茶香面包的做法,红茶面包棒的做法
- JavaScript的Array.flat函数深入探讨
- 买电脑、DIY电脑,你必须了解的避坑技能