Go Memory Management

Source: Internet
Author: User
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.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.