go堆内存分配 | 青训营笔记

95 阅读2分钟

这是我参与「第五届青训营」笔记创作活动的第十二天

Go堆内存分配

堆上的所有对象都会通过mallocgc分配指定大小的内存空间,runtine系列的new、make函数都利用它

func mallocgc(size uintptr, typ *_type, needzero bool) unsafe.Pointer {
  mp := acquirem()
  mp.mallocing = 1
​
  c := gomcache()
  var x unsafe.Pointer
  noscan := typ == nil || typ.ptrdata == 0
  if size <= maxSmallSize {
    if noscan && size < maxTinySize {
      // 微对象分配
    } else {
      // 小对象分配
    }
  } else {
    // 大对象分配
  }
​
  publicationBarrier()
  mp.mallocing = 0
  releasem(mp)
​
  return x
}

从mallocgc函数可以看出它会根据对象的大小选择不同的分配逻辑

  • 微对象 (0, 16B) — 先使用微型分配器,再依次尝试线程缓存、中心缓存和堆分配内存;
  • 小对象 [16B, 32KB] — 依次尝试使用线程缓存、中心缓存和堆分配内存;
  • 大对象 (32KB, +∞) — 直接在堆上分配内存;

接下来会依次梳理内存分配的核心流程

大对象

大于32KB的内存分配请求额外处理,这很好理解。因为最大的内存规格才32KB。所以会直接根据需要的页面数,分配一个新的跨度类为0的span。

image-20230212221541941

微对象

而对于小于16B的内存分配,也不直接匹配预置内存规格。主要是为了减少浪费,如果需要分配16次的1B内存,每次分配时匹配预置的内存规格最小8B,那么每次都会浪费7B。而tiny allocator能够将几个小块的内存分配请求合并,所以16次1B的内存分配请求,可以合并到1个16B的内存块中。诸如此类可以提高内存使用率

image-20230212221656329

那tiny allocator从哪里分配内存呢,上次我们提到过,每个P的mchahe这里,有专门用于tiny allocator的内存mcahe.tiny。 这是一个16B大小的内存单元,mcache.tinyOffset记录这段内存已经用到了哪里。如果tiny allocator要分配size大小的内存空间,而mcahce中的tinyOffset经过对齐后。还够分配size大小的内存,就在tiny内存中直接分配。如果剩余空间不够了,就从当前P的mcache中,找到对应的mspan,重新拿一个16字节大小的内存块来用。如果本地缓存中相应的mspan也没有空间了,就会从mcentral中拿一个新的mspan过来用,分配完以后,如果新拿来的内存块剩余空间,比旧内存块的剩余空间还大,那就用新的内存块把旧的内存块替换掉,这就是tiny allcoator的大致工作流程

image-20230212221837762

小对象

至于最后一种,直接通过本地mcache与全局mcentral配合合作,找到匹配规格的mspan即可

学习地址:www.bilibili.com/video/BV1gT…