likes
comments
collection
share

Go 如何分配堆内存?

作者站长头像
站长
· 阅读数 8

前言

前情回顾:

Go 堆内存结构是什么样的?

我们在使用make,new函数时,都会涉及到堆内存的分配。那Go是如何分配堆内存的呢?

堆结构

简单回顾下堆结构:

Go 如何分配堆内存?

  • heapArena是一个64MB的堆内存,里边有各种类别的mspan
  • 要查找某类别的mspan时,为了效率,使用central作索引中心,通过它来映射到对应heapArena,找到mspan
  • 但central是全局变量,有并发问题。于是每个处理器P各自拥有个mcache本地缓存。先从本地缓存获取mspan,没有再从索引中心查找并获取堆上的mspan

对象分级

Go将不同Object的大小分为三级:

  • Tiny 微对象,大小(0,16B),不能为指针
  • Small 小对象,大小 [16B,32KB]
  • Large 大对象,大小(32KB,+∞)

区别处理:

  • Tiny和Small对象分配到普通的mspan中,普通mspan的类别class在[1,67]范围内
  • Large对象分配到量身定制的mspan中,特殊msan的类别class为0

分配方法

Tiny 微对象

  • 从mcache中获取class为2的mspan, 该mspan每个单元大小为16B
  • 多个微对象合并为一个16B存入mspan单元中

Small 小对象

  • 先从mcache中获取mspan
  • 没有再通过mcentral从heapArena中获取mspan

Large 大对象

  • 直接从堆上的heapArena中分配空间

源码

src\runtime\malloc.gomallocgc方法即堆内存分配主函数,以下为简化版mallocgc,只展示重要部分。

主要大纲:

Go 如何分配堆内存?
func mallocgc(size uintptr, typ *_type, needzero bool) unsafe.Pointer {
    // 0 获取处理器P上的mcache缓存
    mp := acquirem()
    c := getMCache(mp)

    // 1 分配size
    if size <= maxSmallSize { // 要分配的size是否为Tiny或Small对象
       if noscan && size < maxTinySize { // size为微对象
       
            // 1.1 计算偏移量,合并到仍有空间的单元格中
            off := c.tinyoffset
            if size&7 == 0 {
               off = alignUp(off, 8)
            } else if goarch.PtrSize == 4 && size == 12 {
               off = alignUp(off, 8)
            } else if size&3 == 0 {
               off = alignUp(off, 4)
            } else if size&1 == 0 {
               off = alignUp(off, 2)
            }
            if off+size <= maxTinySize && c.tiny != 0 {
               x = unsafe.Pointer(c.tiny + off)
               c.tinyoffset = off + size
               c.tinyAllocs++
               mp.mallocing = 0
               releasem(mp)
               return x
            }
            
          // 1.2 从mcache缓存中获取mspan
          span = c.alloc[tinySpanClass]
          v := nextFreeFast(span) // 获取一个空闲单元内存
          if v == 0 {
             // 1.3 mcache中没有多余的mspan,从central索引中心查找并获取mspan
             v, span, shouldhelpgc = c.nextFree(tinySpanClass) 
          }
          x = unsafe.Pointer(v)
          (*[2]uint64)(x)[0] = 0
          (*[2]uint64)(x)[1] = 0
          size = maxTinySize
       } else { // size为小对象
       
          // 2.1 计算出小对象的类别
          var sizeclass uint8
          if size <= smallSizeMax-8 {
             sizeclass = size_to_class8[divRoundUp(size, smallSizeDiv)]
          } else {
             sizeclass = size_to_class128[divRoundUp(size-smallSizeMax, largeSizeDiv)]
          }
          size = uintptr(class_to_size[sizeclass])
          spc := makeSpanClass(sizeclass, noscan)
          
          // 2.2 从mcache中获取mspan
          span = c.alloc[spc]  
          v := nextFreeFast(span) // 获取一个空闲单元内存
          if v == 0 {
             // 2.3 mcache中没有多余的mspan,从central索引中心查找并获取mspan
             v, span, shouldhelpgc = c.nextFree(spc)
          }
          x = unsafe.Pointer(v)
          if needzero && span.needzero != 0 {
             memclrNoHeapPointers(x, size) // 清除内存之前存留的数据
          }
       }
    } else { // size为大对象
       shouldhelpgc = true
  
       // 3.1 获取大对象的mspan
       span = c.allocLarge(size, noscan) 
       span.freeindex = 1
       span.allocCount = 1
       size = span.elemsize
       x = unsafe.Pointer(span.base())
       if needzero && span.needzero != 0 {
          if noscan {
             delayedZeroing = true
          } else {
             memclrNoHeapPointers(x, size)
          
          }
       }
    }
    
    ...
    return x
}

重要函数nextFree是如何通过mcentral获取mspan的,其路径为:

nextFree -> refill ->

使用mheap_.central[spc].mcentral.uncacheSpan(s),将mcache中爆满的mspan放入mcentral的队列中 ->

然后使用s = mheap_.central[spc].mcentral.cacheSpan(),将空闲mspan出队。从而实现mcache与mcentral各自的mspan交换。->

最终返回获取的mspan

扩展

如果mcache和central中都没有mspan怎么办?

那mheap就要扩容,向操作系统申请一个heapArena了。

在调用nextFree -> refill -> cacheSpan中,如果仍没有mspan的话,就调用

grow ->mheap_.alloc,得到新的heapArena,就有空闲的mspan可用了。

总结

  • Go将对象按大小分为三种
  • 微小对象先用mcache
  • mcache的mspan填满后,与mcentral交换新的空闲mspan
  • mcentral不足后,在新的heapArena中开辟新的mspan
  • 大对象直接在heapArena中开辟新的mspan