前文

  •  

前言

  • 在上文中,我们对于内存、虚拟内存、程序等概念做了简单介绍
  • 在本文中,我们将介绍内存分配以及go语言实现的内存分配方式

内存分配

textdatabssstackheaptextdatabssstackstackstackstackheapmalloccallocfreenewdeletemmap

 

malloc

 

  • 内存分配的算法包括了:
    • K&R malloc
    • Region-based allocator
    • Buddy allocator
    • dlmalloc
    • slab allocator

 

  • 同时,由于算法解决的目标等不同,还会有不同的变种,其他的目标包括:
    • 内存开销小(例如buddy的元数据很大)
    • 良好的内存位置
    • cpu核心增加时,扩展性好
    • 并发malloc / free

 

malloc

TCMalloc(Thread-Caching Malloc)

  • TCMalloc是一种内存分配算法,比GNU C库中的malloc要快2倍,正如其名字一样,其是对于每一个线程构建了缓存内存。
  • TCMalloc解决了多线程时内存分配的锁竞争问题
  • TCMalloc对于小对象的分配非常高效
  • TCMalloc的核心思想是将内存划分为多个级别,以减少锁的粒度。在TCMalloc内部,内存管理分为两部分:小对象内存(thread memory)和大对象内存(page heap)。
  • 小对象内存管理将内存页分成多个固定大小的可分配的free列表。因此,每个线程都会有一个无锁的小对象缓存,这使得在并行程序下分配小对象(<= 32k)非常有效。下图的对象代表的是字节。

内存页

 

页页堆(page heap)

 

连续页

  • go内存分配器最初是基于TCMalloc的

go内存分配

mspan

runtime/mheap.go
type mspan struct {
    next *mspan     // next span in list, or nil if none
    prev *mspan     // previous span in list, or nil if none
    list *mSpanList // For debugging. TODO: Remove.
    startAddr uintptr // address of first byte of span aka s.base()
    npages    uintptr // number of pages in span
    manualFreeList gclinkptr // list of free objects in mSpanManual spans
    freeindex uintptr
    nelems uintptr // number of object in the span.
    allocCache uint64
    allocBits  *gcBits
    gcmarkBits *gcBits
    sweepgen    uint32
    divMul      uint16        // for divide by elemsize - divMagic.mul
    baseMask    uint16        // if non-0, elemsize is a power of 2, & this will get object allocation base
    allocCount  uint16        // number of allocated objects
    spanclass   spanClass     // size class and noscan (uint8)
    state       mSpanStateBox // mSpanInUse etc; accessed atomically (get/set methods)
    needzero    uint8         // needs to be zeroed before allocation
    divShift    uint8         // for divide by elemsize - divMagic.shift
    divShift2   uint8         // for divide by elemsize - divMagic.shift2
    elemsize    uintptr       // computed from sizeclass or from npages
    limit       uintptr       // end of data in span
    speciallock mutex         // guards specials list
    specials    *special      // linked list of special records sorted by offset.
}

  • 如上图,mspan是一个双向链接列表对象,其中包含页面的起始地址,它具有的页的数量以及其大小。
  • mspan有三种类型,分别是:
    • idle:没有对象,可以释放回操作系统;或重新用于堆内存;或重新用于栈内存
    • in use:至少具有一个堆对象,并且可能有更多空间
    • stack:用于协程栈。可以存在于栈中,也可以存在于堆中,但不能同时存在于两者中。

 

mcache

  • Go 像 TCMalloc 一样为每一个 逻辑处理器(P)(Logical Processors) 提供一个本地线程缓存(Local Thread Cache)称作 mcache,所以如果 Goroutine 需要内存可以直接从 mcache 中获取,由于在同一时间只有一个 Goroutine 运行在 逻辑处理器(P)(Logical Processors) 上,所以中间不需要任何锁的参与。mcache 包含所有大小规格的 mspan 作为缓存。

  • 对于每一种大小规格都有两个类型:
    • scan -- 包含指针的对象。
    • noscan -- 不包含指针的对象。

 

  • 采用这种方法的好处之一就是进行垃圾回收时 noscan 对象无需进一步扫描是否引用其他活跃的对象。

mcentral

  • mcentral是被所有逻辑处理器共享的
  • mcentral 对象收集所有给定规格大小的 span。每一个 mcentral 都包含两个 mspan 的列表:
    • empty mspanList -- 没有空闲对象或 span 已经被 mcache 缓存的 span 列表
    • nonempty mspanList -- 有空闲对象的 span 列表

  • 每一个 mcentral 结构体都维护在 mheap 结构体内。

mheap

  • Go 使用 mheap 对象管理堆,只有一个全局变量。持有虚拟地址空间。
  • 就上我们从上图看到的:mheap 存储了 mcentral 的数组。这个数组包含了各个的 span 的 mcentral。
central [numSpanClasses]struct {
    mcentral mcentral
    pad      [unsafe.Sizeof(mcentral{})%sys.CacheLineSize]byte
}
free[_MaxMHeapList]mSpanList

 

  • Small类会被分为大约有70个大小,每一个大小都拥有一个free list
  • 引入Tiny这一微小对象是为了适应小字符串和独立的转义变量。
  • Tiny微小对象将几个微小的分配请求组合到一个16字节的内存块中
  • 当分配Tiny对象时:
    • 查看协程的mcache的相应tiny槽
    • 根据分配对象的大小,将现有子对象(如果存在)的大小四舍五入为8、4或2个字节
    • 如果当前分配对象与现有tiny子对象适合,请将其放置在此处

 

mcachemspanmspanbitmap

 

mspanmcentralmspanmspan

 

mspanmheap

 

mheap

 

mheapmspan.freelarge

总结

垃圾回收

参考资料