golang内存管理

解释定义:
mheap:用于管理整个堆内存,mheap 管理多个arena,arena管理多个span,一个span由多个page组成,一个arena有8192个page,page由内存块组成
mspan:一个span对应一个mspan
mcentral:mheap里有一个全局的mspan管理中心包含mcentral数组,0-135 mcentral,一个mcentral(管理对应类型的span)

spanclass高7为是大小编号,最后一位0代表是指针,1代表不是指针

另外分已用尽full和未用尽partial,里面又分已清扫的spanSet和未清扫的spanSet,spanSet是并发安全的。如果所有人都来mcentral要内存,那加锁太频繁了,那么自然而然每个P都有本地的小对象缓存
mcache:每个P内部的小对象缓存。alloc 0-135 *mspan,先在本地找,不够了去mcentral拿一个到本地,用完的放到mcentral的fullset中,tiny 16B,tinyoffset记录这段内存已经用到哪里了,剩下空间还够就继续分配,不够就拿16B大小的内存块过来用,本地缓存的用完了就从mcentral中拿一个新的mspan过来
heapArena:arena 对应一个heapArena 管理者
type heapArena struct { bitmap [heapArenaBitmapBytes]byte spans [pagesPerArena]*mspan pagesPerArena = 8192 用于定位一个page对应的mspan在哪 pageInUse [pagesPerArena / 8]uint8 pageMarks [pagesPerArena / 8]uint8 pageSpecials [pagesPerArena / 8]uint8 checkmarks *checkmarksMap zeroedBase uintptr 标记此 arena 中尚未使用的第一个page的首地址 }

bitmap最后的byte代表一字节分高4位代表终止扫描,低4位代表标量指针。
假设分配一个slice,包含指针,长度,容量。pageInUse 只标记处于使用状态的span的第一个page,例如arena的span 0-1 2-4都使用了,那么第0号元素的pageInUse 第0位标记为1 第2位标记为1

pageMarks 同pageInUse,只标记哪些span中存在被标记的对象,在gc的时候通过这个位图来释放不含标记对象的span

spans 用于定位一个page对应的mspan在哪

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 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
// TODO: Look up nelems from sizeclass and remove this field if it
// helps performance.
nelems uintptr // number of object in the span.
// Cache of the allocBits at freeindex. allocCache is shifted
// such that the lowest bit corresponds to the bit freeindex.
// allocCache holds the complement of allocBits, thus allowing
// ctz (count trailing zero) to use it directly.
// allocCache may contain bits beyond s.nelems; the caller must ignore
// these.
allocCache uint64
// allocBits and gcmarkBits hold pointers to a span's mark and
// allocation bits. The pointers are 8 byte aligned.
// There are three arenas where this data is held.
// free: Dirty arenas that are no longer accessed
// and can be reused.
// next: Holds information to be used in the next GC cycle.
// current: Information being used during this GC cycle.
// previous: Information being used during the last GC cycle.
// A new GC cycle starts with the call to finishsweep_m.
// finishsweep_m moves the previous arena to the free arena,
// the current arena to the previous arena, and
// the next arena to the current arena.
// The next arena is populated as the spans request
// memory to hold gcmarkBits for the next GC cycle as well
// as allocBits for newly allocated spans.
//
// The pointer arithmetic is done "by hand" instead of using
// arrays to avoid bounds checks along critical performance
// paths.
// The sweep will free the old allocBits and set allocBits to the
// gcmarkBits. The gcmarkBits are replaced with a fresh zeroed
// out memory.
allocBits *gcBits
gcmarkBits *gcBits
// 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
divMul uint32 // for divide by elemsize
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
allocCountBeforeCache uint16 // a copy of allocCount that is stored just before this span is cached
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.
// freeIndexForScan is like freeindex, except that freeindex is
// used by the allocator whereas freeIndexForScan is used by the
// GC scanner. They are two fields so that the GC sees the object
// is allocated only when the object and the heap bits are
// initialized (see also the assignment of freeIndexForScan in
// mallocgc, and issue 54596).
freeIndexForScan uintptr
}
spanclass 跟mcentral一样记录着划分好的内存块规格
nelem 记录当前span共划分成了多少个内存块
freeIndex 记录着下个空闲内存块的索引
allocBits *gcBits 用于标记哪些内存块已经被分配了
gcmarkBits *gcBits 当前span的标记位图,一个二进制位对应span的一个内存块,gc清扫的时候释放allocBits,然后把gcmarkBits用作allocBits,未被gc标记的内存块就能回收利用了

申请内存的步骤:
1.辅助gc
至少扫描64KB,多干的部分会存在银行。全局gccontroller有足够多的信用,窃取了也不用进行辅助gc
2.空间分配
<16B noscan tiny
>=16B <=32KB noscan scannable
>32KB 大块内存分配器 需要多少字节就分配一个对应大小的新的span
3.位图标记
*heapArena放到一个数组里,并用arena编号作为索引定位heapArena windows/linux架构arena大小不同,所以有二维数组的说法,计算出arenaidx
amd64 windows第一维64,占6bit,第二维数组长度为1M=2^20,arenaidx中低20位用作第二维的索引
amd64 linux第一维一个元素,第二维4M=2^22,arenaidx中低22位用作第二维的索引
每个arena中有pagesPerArena个page,每个page大小是pageSize,一个内存块的page编号=(p/pageSize)%pagesPerArena,最终能在heapArena数组中找到mspan地址了
4.收尾工作
当前处于gc标记阶段,需要对新分配的对象进行gc标记,这次内存分配达到了触发gc的条件,还会触发新一轮的gc

浙公网安备 33010602011771号