golang 内存分配与管理
1. 内存分配
- arena 即为所谓的堆区,应用中需要的内存从这里分配, 大小为 512G,为了方便管理把 arena 区域划分成一个个的 page,每个 page 为 8KB,一共有 512GB/8KB个页。
- spans 区域存放 span 的指针,每个指针对应一个 page,所以 span 区域的大小为 (512GB/8KB) * 指针大小8byte = 512M。
- bitmap 区域大小也是通过 arena 计算出来 512GB / (指针大小(8 byte) * 8 / 2) = 16G,即 bitmap 的一个字节对应 arena 的32 个字节。用于表示 arena 区域中哪些地址保存了对象, 并且对象中哪些地址包含了指针,主要用于GC。
2. 分配细节
- object size > 32K,则使用 mheap 直接分配。
- object size < 16 byte,不包含指针使用 mcache 的小对象分配器 tiny 直接分配;包含指针分配策略与[16 B, 32 K]类似。
- object size >= 16 byte && size <=32K byte 时,先使用 mcache 中对应的 size class 分配。
- 如果 mcache 对应的 size class 的 span 已经没有可用的块,则向 mcentral 请求。
- 如果 mcentral 也没有可用的块,则向 mheap 申请,并切分。
- 如果 mheap 也没有合适的 span,则向操作系统申请。
3. span
可以看出span是一个非常重要的数据结构,每个span包含若干个连续的page。
小对象分配会在span page中划分更小的粒度;大对象通过多页实现。
4. size class
go1.10\src\runtime\sizeclasses.go
// class bytes/obj bytes/span objects tail waste max waste // 1 8 8192 1024 0 87.50% // 2 16 8192 512 0 43.75% // 3 32 8192 256 0 46.88% // 4 48 8192 170 32 31.52% // 5 64 8192 128 0 23.44% // 6 80 8192 102 32 19.07% // 7 96 8192 85 32 15.95% // 8 112 8192 73 16 13.56% // 9 128 8192 64 0 11.72% // 10 144 8192 56 128 11.82% // ... // 65 28672 57344 2 0 4.91% // 66 32768 32768 1 0 12.50%
上表中每列含义如下:
- class: class ID,每个span结构中都有一个class ID, 表示该span可处理的对象类型
- bytes/obj:该class代表对象的字节数
- bytes/span:每个span占用堆的字节数,也即页数*页大小
- objects: 每个span可分配的对象个数,也即(bytes/spans)/(bytes/obj)
- tail bytes: 每个span产生的内存碎片,也即(bytes/spans)%(bytes/obj)
上表可见最大的对象是32K大小,超过32K大小的由特殊的class表示,该class ID为0,每个class只包含一个对象。所以上面只有列出了1-66。
有点像装箱算法,按照规格分配,减少内存碎片。
5. struct
span是内存管理的基本单位,每个span用来管理特定的size class对象,根据size class,span将若干个页分成块进行管理。
go1.10\src\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 startAddr uintptr // address of first byte of span aka s.base() npages uintptr // number of pages in span nelems uintptr // number of object in the span. allocBits *gcBits gcmarkBits *gcBits allocCount uint16 // number of allocated objects spanclass spanClass // size class and noscan (uint8) elemsize uintptr // computed from sizeclass or from npages }
以size class 10为例,npages=1,nelems=56,spanclass=10,elemsize=144;startAddr指arena区位置;next和prev指spans区,span链表;allocBits是一个bitmap,标记分配块分配情况,这个设计我也用过,之前用redis bitmap实现了IPAM。
6. cache
从上面我们知道go通过span来分配内存,那在哪里用span?通过之前的学习Go语言——goroutine并发模型,我们知道每个P都有mcache,通过mcache管理每个G需要的内存。
go1.10\src\runtime\mcache.go
type mcache struct { tiny uintptr tinyoffset uintptr alloc [numSpanClasses]*mspan // spans to allocate from, indexed by spanClass } numSpanClasses = _NumSizeClasses << 1 _NumSizeClasses = 67
alloc是span数组,长度是67 << 1,说明每种size class有2组元素。第一组span对象中包含了指针,叫做scan,表示需要gc scan;第二组没有指针,叫做noscan。提高gc scan性能。
mcache初始没有span,G先从central动态申请span,并缓存在cache。
7. central
go1.10\src\runtime\mcentral.go
type mcentral struct { lock mutex spanclass spanClass nonempty mSpanList // list of spans with a free object, ie a nonempty free list empty mSpanList // list of spans with no free objects (or cached in an mcache) // nmalloc is the cumulative count of objects allocated from // this mcentral, assuming all spans in mcaches are // fully-allocated. Written atomically, read under STW. nmalloc uint64 }
- lock: 多个G并发从central申请span,所以需要lock,保证一致性
- spanclass : 每个mcentral管理着一组有相同size class的span列表
- nonempty: 指还有内存可用的span列表
- empty: 指没有内存可用的span列表
- nmalloc: 指累计分配的对象个数
线程从central获取span步骤如下:
- 加锁
- 从nonempty列表获取一个可用span,并将其从链表中删除
- 将取出的span放入empty链表
- 将span返回给线程
- 解锁
- 线程将该span缓存进cache
线程将span归还步骤如下:
- 加锁
- 将span从empty列表删除
- 将span加入nonempty列表
- 解锁
8. heap
central只管理特定的size class span,所以必然有一个更上层的数据结构,管理所有的sizeclass central,这就是heap。
go1.10\src\runtime\mheap.go
type mheap struct { lock mutex spans []*mspan // Malloc stats. largealloc uint64 // bytes allocated for large objects nlargealloc uint64 // number of large object allocations largefree uint64 // bytes freed for large objects (>maxsmallsize) nlargefree uint64 // number of frees for large objects (>maxsmallsize) // range of addresses we might see in the heap bitmap uintptr // Points to one byte past the end of the bitmap bitmap_mapped uintptr arena_start uintptr arena_used uintptr // Set with setArenaUsed. arena_alloc uintptr arena_end uintptr arena_reserved bool central [numSpanClasses]struct { mcentral mcentral pad [sys.CacheLineSize - unsafe.Sizeof(mcentral{})%sys.CacheLineSize]byte } }
- spans:映射span -> page
- large:大对象,>32K
- bitmap: gc
- arena: arena区相关信息,pages,堆区
- central:通过size class管理span,每种size class对应两个central