This is a creation in Article, where the information may have evolved or changed.
Memory management Cache Structure
Go implements memory management with the Tcmalloc architecture, with Goroutine and garbage collection. the basic strategy of Tcmalloc is to divide the memory into multiple levels. The request object takes precedence from the minimum level of memory management collection mcache , if the Mcache cannot hit the need to request a batch of memory block cache to the local mcache, if mcentral there is no free memory block, to the mheap request to populate the mcentral, Finally, apply to the system.
Mcache + Mspan
The minimum level of memory block Management collection mcache is maintained by Goroutine itself, so that memory is not locked out from the application. It is an array of size 67, with different index corresponding to different specifications mspan . newobjectthe sizetoclass Mspan object is obtained by calculating the corresponding specification and then Mcache.
type mcache struct { alloc [_NumSizeClasses]*mspan // spans to allocate from}
mspanContains a batch of the same size of idle object , looked up by the freelist pointer. the object inside the Mspan is a contiguous block of memory, which is a contiguous contiguous memory space of N page (4KB). The space is then evenly divided into objects of the same size, which are concatenated into the linked list . When NewObject find the corresponding specification in the Mcache Mspan, from its freelist to take an object.
type mspan struct { next *mspan // in a span linked list prev *mspan // in a span linked list start pageID // starting page number npages uintptr // number of pages in span freelist gclinkptr // list of free objects sizeclass uint8 // size class incache bool // being used by an mcache}
Mheap + mcentral
If there is no freeobject in the span of a specification, it is necessary to mcentral obtain the Mspan of this specification. Just Mcentral is also stored in the array according to the class specification, as long as the specifications to go to mheap the mcentral array to take Mspan is good.
// 某种规格的mspan正好对应一个mcentraltype mcentral struct { lock mutex sizeclass int32 nonempty mspan //还有空闲object的mspan empty mspan //没有空闲object或已被cache取走的mspan}
If the mcentral of this specification in the central array is not freespan, it needs to be mheap fetched from the free array. Here the specifications are not aligned, so should be re-cut into the corresponding specifications of the Mspan.
type mheap struct { lock mutex free [_MaxMHeapList]mspan // 页数在127以内的空闲span链表 freelarge mspan spans **mspan bitmap uintptr bitmap_mapped uintptr arena_start uintptr arena_used uintptr arena_end uintptr arena_reserved bool central [_NumSizeClasses]struct { mcentral mcentral pad [_CacheLineSize]byte } spanalloc fixalloc // allocator for span* cachealloc fixalloc // allocator for mcache*}
Initialization of the memory
I had seen this picture very early, and was mistaken in his understanding because I missed a word struct Mcache alloc from 'cachealloc' by FixAlloc . That is, the user process NewObject is allocated from the arena region, and the runtime layer itself management structure such as Mcache is specifically designed Fixalloc to allocate, the reason may be that these runtime layer management object type and length are relatively fixed, And the life cycle is very long, not suitable to occupy the arena area.
Mallocinit
By sysReserve applying a contiguous memory Spans+bitmap+arenato the system. Where arena is the allocated memory block provided for each level cache structure, spans is an array of pointers used to address the Arena area by page.
The final Sysreserve call is a system call mmap . Applied for 512GB virtual address space, real physical memory is used when the use of the time when the pages are really occupied.
func mallocinit () {//Initialize specification class and size in a controlled manner initsizes () if ptrsize = = 8 && (limit = = 0 | | limit > 1<<30) {arenasize: = Round (_maxmem, _pagesize) bitmapsize = arenasize/(ptrsize * 8/4) spanssize = Arena Size/_pagesize * ptrsize pSize = bitmapsize + spanssize + arenasize + _pagesize p1 = uintptr (Sysreserve (UN Safe. Pointer (P), PSize, &reserved)} Mheap_.spans = (**mspan) (unsafe. Pointer (p1)) Mheap_.bitmap = p1 + spanssize Mheap_.arena_start = p1 + (spanssize + bitmapsize) mheap_.arena_used = Mheap_.arena_start Mheap_.arena_end = p + pSize mheap_.arena_reserved = reserved Mheap_init (&MHEAP_, SpansS ize) _g_: = GETG () _g_.m.mcache = Allocmcache ()}
The
Mheap initializes the relevant pointer so that it can address the arena memory. Initialize the cachealloc fixed allocator at the same time. The last execution of M.mcache = Allocmcache () is initialized each time the Gouroutine is created. It was not until then that Mcache was actually created, and the mspan of the entire array in the Mcache was initialized to Emptyspan.
func (h *mheap) init(spansStart, spansBytes uintptr) { h.spanalloc.init(unsafe.Sizeof(mspan{}), recordspan, unsafe.Pointer(h), &memstats.mspan_sys) h.cachealloc.init(unsafe.Sizeof(mcache{}), nil, nil, &memstats.mcache_sys) h.spanalloc.zero = false for i := range h.free { h.free[i].init() h.busy[i].init() } h.freelarge.init() h.busylarge.init() for i := range h.central { h.central[i].mcentral.init(int32(i)) } sp := (*slice)(unsafe.Pointer(&h.spans)) sp.array = unsafe.Pointer(spansStart) sp.len = 0 sp.cap = int(spansBytes / sys.PtrSize)}func allocmcache() *mcache { // lock and fixalloc mcache c := (*mcache)(mheap_.cachealloc.alloc()) for i := 0; i < _NumSizeClasses; i++ { c.alloc[i] = &emptymspan } return c}
Fixalloc
The Fixalloc allocator initializes the size of each allocation by Init. Chunk is a fixed-size block of memory allocated each time, list is the memory block linked list. When Fixalloc is initialized to Cachealloc, the Alloc is assigned a piece of mcache each time it is called. Persistantalloc appears to be a place where runtime has a backup memory of the global storage, taking precedence from here to no longer mmap a piece from the system.
type fixalloc struct { size uintptr first func(arg, p unsafe.Pointer) arg unsafe.Pointer list *mlink chunk unsafe.Pointer nchunk uint32 inuse uintptr // in-use bytes now stat *uint64 zero bool // zero allocations}func (f *fixalloc) alloc() unsafe.Pointer { // 优先从可复用链表中获取对象块 if f.list != nil { f.list = f.list.next return v } // 如果没有从系统申请chunk大小的内存块 if uintptr(f.nchunk) < f.size { f.chunk = persistentalloc(_FixAllocChunk, 0, f.stat) } v := f.chunk // 为调用方提供了fist函数作为hook点 return v}
Memory allocation
Mallocgc
The following summarizes the process of malloc, and the basic trivial object is to find the corresponding specification of Mspan from Mcache, and get the object Object memory block on Freelist. nextfree hides the lookup and flow direction of the entire memory data block.
Func MALLOCGC (Size uintptr, Typ *_type, needzero bool) unsafe. Pointer {c: = Gomcache () if size <= maxsmallsize {//size less than 16bit without scanning objects directly from Mcache tiny if Nosc An && size < Maxtinysize {off: = C.tinyoffset if off+size <= maxtinysize && C . Tiny! = 0 {x = unsafe. Pointer (C.tiny + Off) return X}//If there is no tiny, then find the span from the Mspan of the corresponding specification in Mcache: = C.alloc[tinysizeclass] V, _, SHOULDHELPGC = C.nextfree (tinysizeclass) x = unsafe. Pointer (v)} else {//normal less than 4KB small object first calculated specification span: = C.alloc[sizeclass] V, span, SHOULDH ELPGC = C.nextfree (sizeclass)}} else {//large objects are allocated directly from the heap span Systemstack (func () {s = Largealloc (Size, Needzero)}) x = unsafe. Pointer (S.base ())} return X}func (c *mcache) Nextfree (Sizeclass uint8) (v Gclinkptr, s *mspan, SHOULDHELPGC bool) { s =C.alloc[sizeclass] FreeIndex: = S.nextfreeindex () if FreeIndex = = s.nelems {Systemstack (func () { C.refill (Int32 (Sizeclass))}) s = c.alloc[sizeclass] FreeIndex = S.nextfreeindex ()} v = Gclink PTR (Freeindex*s.elemsize + s.base ()) return}
Refill + Cachespan
If you nextfree do not get an object in the Mspan of the mcache corresponding specification, then you need a block of memory from the Mcentral refill .
There is a detail to be alloc the central Plains has no usable object of this piece of the Mspan back to Central, should be placed in the empty linked list. This just sets the corresponding Mspan Incache to False, waiting for the sweep to be recycled.
func (c *mcache) refill(sizeclass int32) *mspan { s := c.alloc[sizeclass] if s != &emptymspan { s.incache = false } s = mheap_.central[sizeclass].mcentral.cacheSpan() c.alloc[sizeclass] = s return s}
Sweepgen is a collection token that, when sweepgen=sg-2, indicates that it is being reclaimed, sweepgen-1 indicates that it is being reclaimed, and Sweepgen indicates that it has been recycled. when obtaining mspan from mcentral, it is possible that the current span is waiting or recovering, and we are going to return the Mspan waiting to be recycled to refill mcache, so insert it into the empty list.
func (c *mcentral) cacheSpan() *mspan { sg := mheap_.sweepgenretry: var s *mspan for s = c.nonempty.first; s != nil; s = s.next { if s.sweepgen == sg-2 && atomic.Cas(&s.sweepgen, sg-2, sg-1) { // 等待回收 可以返回使用 c.nonempty.remove(s) c.empty.insertBack(s) s.sweep(true) goto havespan } if s.sweepgen == sg-1 { // 正在回收 忽略 continue } c.nonempty.remove(s) c.empty.insertBack(s) goto havespan } for s = c.empty.first; s != nil; s = s.next {...} s = c.grow() c.empty.insertBack(s)havespan: ... return s}
Mcentral Grow
If there is no mspan in mcentral, then you need grow, which is obtained from MHEAP. to calculate the page number corresponding to the current specification, go directly to Npage's Mspan from Mheap. The free area is an array of pointers, each of which corresponds to a mspan list, and the array is addressed by Npage. If there are no idle mspan in the list of npage that are larger than required, the mheap also needs to be expanded.
func (c *mcentral) grow() *mspan { npages := uintptr(class_to_allocnpages[c.sizeclass]) size := uintptr(class_to_size[c.sizeclass]) n := (npages << _PageShift) / size s := mheap_.alloc(npages, c.sizeclass, false, true) heapBitsForSpan(s.base()).initSpan(s) return s}func (h *mheap) allocSpanLocked(npage uintptr) *mspan { for i := int(npage); i < len(h.free); i++ { list = &h.free[i] if !list.isEmpty() { s = list.first goto HaveSpan } } list = &h.freelarge s = h.allocLarge(npage) if s == nil { if !h.grow(npage) { return nil } s = h.allocLarge(npage) }HaveSpan: // Mark span in use. return s}
Mheap Grow
The expansion of Mheap h.sysAlloc directly to the arena area Nbytes memory, the number according to the Npage size calculation. Some pointer markers in the arena area begin to move, eventually adding Mspan to the list, waiting to be allocated.
func (h *mheap) grow(npage uintptr) bool { ask := npage << _PageShift v := h.sysAlloc(ask) s := (*mspan)(h.spanalloc.alloc()) s.init(uintptr(v), ask>>_PageShift) p := (s.base() - h.arena_start) >> _PageShift for i := p; i < p+s.npages; i++ { h.spans[i] = s } atomic.Store(&s.sweepgen, h.sweepgen) s.state = _MSpanInUse h.pagesInUse += uint64(s.npages) // 加入链表 h.freeSpanLocked(s, false, true, 0) return true}
Memory Recovery and release
Simply say two words: Mspan in the Sweepgen recycling mark, the recovered memory will first all back to Mcentral. If all the Mspan have been recycled, then it can be returned to Mheap's freelist. The recovered memory blocks are, of course, for reuse and are not released directly.
func (s *mspan) sweep(preserve bool) bool { res = mheap_.central[cl].mcentral.freeSpan(s, preserve, wasempty)}func (c *mcentral) freeSpan(s *mspan, preserve bool, wasempty bool) bool { if wasempty { c.empty.remove(s) c.nonempty.insert(s) } ... c.nonempty.remove(s) mheap_.freeSpan(s, 0) return true}
Monitoring thread Sysmon again, it iterates through all the Mspan in the free freelarge in Mheap, and finds that idle time exceeds the threshold to madvise recommend that the kernel release its associated physical memory.