解释定义:
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