基本概念
## 基本策略:
- 先从操作系统申请一块大内存,以减少系统调用
- 将申请到的内存按照特定大小预先切成小块,构成一个链表
- 为对象分配内存时,只需从链表中取出一个大小合适的块使用就好
- 回收对象内存是,只需将对象放回原链表,以便服用
- 闲置过多时,会将部分内存归还系统,降低整体开销
内存块
分配器将其管理的内存块分成两种:
- span: 有多个地址连续的页(page)组成的大块内存
- object: 将span按特定大小分成多个小块, 每个小块可存储一个对象
用途上来说,span面向的是内部管理,object面向的是对象的分配.
分配器按页数大小来区分不同大小的span。比如,以页数为单位将span存放到管理数组中,需要的时候就可以以页数为索引进行查找.
在runtime/sizeclasses.go
中写了预定义的大小
const (
_MaxSmallSize = 32768 //最大小对象size, 32*1024byte
smallSizeDiv = 8
smallSizeMax = 1024
largeSizeDiv = 128
_NumSizeClasses = 67 //size类别数量
_PageShift = 13
_PageSize = 1 << _PageShift // 8kb
)
这里展示了mspan的结构,
spanclass指定了是那种size-class
freeindex则是用于发现下一个free object用的
allocBits 是gc bitmap
sweepgen 用来标志gc清理状态
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
manualFreeList gclinkptr // list of free objects in mSpanManual spans
// freeindex is the slot index between 0 and nelems at which to begin scanning
// for the next free object in this span.
// Each allocation scans allocBits starting at freeindex until it encounters a 0
// indicating a free object. freeindex is then adjusted so that subsequent scans begin
// just past the newly discovered free object.
//
// If freeindex == nelem, this span has no free objects.
//
// allocBits is a bitmap of objects in this span.
// If n >= freeindex and allocBits[n/8] & (1<<(n%8)) is 0
// then object n is free;
// otherwise, object n is allocated. Bits starting at nelem are
// undefined and should never be referenced.
//
// Object n starts at address n*elemsize + (start << pageShift).
freeindex uintptr
allocBits *gcBits
spanclass spanClass // size class and noscan (uint8)
elemsize uintptr // computed from sizeclass or from npages
// sweep generation:
// if sweepgen == h->sweepgen - 2, the span needs sweeping
// if sweepgen == h->sweepgen - 1, the span is currently being swept
// if sweepgen == h->sweepgen, the span is swept and ready to use
// if sweepgen == h->sweepgen + 1, the span was cached before sweep began and is still cached, and needs sweeping
// if sweepgen == h->sweepgen + 3, the span was swept and then cached and is still cached
// h->sweepgen is incremented by 2 after every GC
sweepgen uint32
}
size-class的分配通过runtime/mksizeclasses.go
生成:
size根据align递增,同时对齐, 这里计算了最小浪费和最大浪费
align := 8
for size := align; size <= maxSmallSize; size += align {
if powerOfTwo(size) { // bump alignment once in a while
if size >= 2048 {
align = 256
} else if size >= 128 {
align = size / 8
} else if size >= 16 {
align = 16 // required for x86 SSE instructions, if we want to use them
}
}
spanSize := c.npages * pageSize
objects := spanSize / c.size
tailWaste := spanSize - c.size*(spanSize/c.size) //这里的浪费是对齐引起的浪费
maxWaste := float64((c.size-prevSize-1)*objects+tailWaste) / float64(spanSize) // 这里是对其浪费再加上装入前一个size-class的对象引起的浪费
管理组件
正如上篇文章讲的,这里用到的是cache,central,heap三个组件来管理内存
runtime/mheap.go
type mheap struct {
// lock must only be acquired on the system stack, otherwise a g
// could self-deadlock if its stack grows with the lock held.
lock mutex
free mTreap // free spans
// central free lists for small size classes.
// the padding makes sure that the mcentrals are
// spaced CacheLinePadSize bytes apart, so that each mcentral.lock
// gets its own cache line.
// central is indexed by spanClass.
central [numSpanClasses]struct {
mcentral mcentral
pad [cpu.CacheLinePadSize - unsafe.Sizeof(mcentral{})%cpu.CacheLinePadSize]byte
}
}
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
}
runtime/mcache.go
type mcache struct {
// tiny is a heap pointer. Since mcache is in non-GC'd memory,
// we handle it by clearing it in releaseAll during mark
// termination.
tiny uintptr
tinyoffset uintptr
local_tinyallocs uintptr // number of tiny allocs not counted in other stats
alloc [numSpanClasses]*mspan // spans to allocate from, indexed by spanClass
}
分配流程
numSpanClasses = _NumSizeClasses << 1
- 计算待分配对象的规格(size-class)
- 先找到对应sizeclass的span,根据span.freeindex尝试nextFreeFast(将下一个空闲对象地址放在这里,可快速获取),从span.allocCache中查找是否有空闲的object,如果有则直接返回
- 从cache.alloc数组中知道相同size-class的span,如果存在则返回
- 如果不存在,会在之后启动gc,同时通过
mheap_.central[spc].mcent