Go Garbage Collection

Source: Internet
Author: User
This is a creation in Article, where the information may have evolved or changed.

Typically C + + reclaims objects by pointer reference counts, but this does not handle circular references . In order to avoid the defect of reference counting, a garbage collection algorithm, such as Mark clearing and generational, was later appeared. Go's garbage collection officials describe the non-compact write barrier concurrency tag cleanup . The literal interpretation of the markup cleanup algorithm is to mark the block of memory that is available mark , and the last non-tagged block of memory will be cleaned up sweep .

Tri-Color Labeling method

Judging whether an object is garbage or not requires a tag, see if you can refer directly or indirectly to the object from the current stack or from the global data area. This initial current goroutine stack and global data area is called the root area of the GC. The scan starts here by markroot marking all of the root area pointers as accessible, and then scanning along these pointers to recursively mark all the objects that are encountered. This leads to several questions:

  1. Tag cleanup can be concurrent with user code
  2. How to get the type of an object and find out where all of the available area marker bits are recorded
  3. When to trigger tag cleanup

How to tag concurrently

The tag sweep algorithm needs to stop all goroutine when marking and cleaning to ensure that the marked area is not modified by the user, causing a cleanup error. But every time the GC is Stoptheworld it is obviously unacceptable. Various versions of Go have made a variety of efforts to reduce STW. Starting from Go1.5, three-color labeling method is used to realize the concurrency of the tagging phase.

    • At first, all the objects were white.
    • Scan all available objects, mark as Gray, and put in the pending queue
    • Extracts a gray object from a queue, marks its reference object as gray and puts it in a queue, with itself marked black
    • Write barrier monitor object memory modification, re-color or put into queue

If the object is not white or black after the mark is finished, the cleanup operation only needs to reclaim the white object to reclaim the memory.

probably understand the so-called concurrency tag, the first is to be able to work with the user code concurrency, followed by the markup is not recursive, but multiple goroutine concurrency . The former solves the concurrency problem through Write-barrier, which realizes the non-recursive mark can reach the object through the Gc-work queue.

Write-barrier

Use the following example to explain the problem of concurrency, which is referenced from the CMS garbage collector principle. When the Reference object B is found from a GC root, B turns gray a to black. At this point, the user goroutine executes a to B reference to A to C reference, and b no longer references C. Then the GC goroutine, found that B did not reference the object, B became black. At this point, because a has turned black to complete the scan, C will be treated as a white unreachable object is cleared.

Workaround: introduce a write barrier . When you find that a has been marked black, if A and C, then the C gray to the queue. This write_barrier the compiler to generate a small piece of code before each memory write operation.

// 写屏障伪代码write_barrier(obj,field,newobj){    if(newobj.mark == FALSE){        newobj.mark = TRUE        push(newobj,$mark_stack)    }    *field = newobj}

Gc-work

How a non-recursive implementation traverses the Mark's reach node clearly requires a queue.

This queue also helps to distinguish between black objects and gray objects because the marker bit has only one. Marked and in the queue is a gray object, a black object that is marked but not in the queue, and the last marked is a white object.

root node queuewhile(queue is not nil) {  dequeue // 节点出队  process // 处理当前节点   child node queue // 子节点入队}

Summarize the procedure for concurrent tagging:

    1. gcstartn is prepared for the start phase goMarkWorkers . Each worker processes the following same process.
    2. For the first time, Mark markroot will first queue all the pointers to the root area.
    3. The node out of the scanobject queue is black, taking the node out of the GCW to start scanning processing.
    4. Retrieves the type information for all child nodes of the node when the scan is not a pointer, and if the pointer is not marked, the greyobject queue.
    5. Each worker goes to GCW to take the task until it is empty.
Each markworker executes gcdrain this token process func gcdrain (GCW *gcwork, Flags Gcdrainflags) {//If no root area is queued then Markroot Markroot (GC W, job) if idle && pollwork () {goto done}//node out of Team B = Gcw.get () scanobject (b, GCW) done:}f UNC Scanobject (b uintptr, GCW *gcwork) {hbits: = Heapbitsforaddr (b) S: = spanofunchecked (b) N: = s.elemsize F or i = 0; I < n; i + = sys.        ptrsize {//Find bits for this word.        If Bits&bitpointer = = 0 {continue//not a pointer} ....//Mark the object. If obj, hbits, span, Objindex: = Heapbitsforobject (obj, b, i); Obj! = 0 {greyobject (obj, b, I, hbits, span, GCW, Objindex)}} gcw.bytesmarked + = UInt64 (n) G Cw.scanwork + = Int64 (i)}func greyobject (obj, base, off UIntPtr, Hbits heapbits, span *mspan, GCW *gcwork, Objindex UI    NTPTR) {mbits: = Span.markbitsforindex (Objindex)//If marked we have no to do. If mbits.ismarked () {return   If!hbits.haspointers (span.elemsize) {return} gcw.put (obj)} 

Mark Bit

The precondition for an accurate garbage collection is to obtain the type information of the object area to determine if it is a pointer. How to judge, and finally put the standard to remember where: through the heap area arena front corresponding bitmap.

Structs do not contain pointers, but they do not need to be recursively labeled struct members. If there is no type information, it can only be recursively labeled for all struct members. Also, if a non-pointer member just stores the content corresponding to the legal address, then the object of this address will happen to be flagged, resulting in the inability to recycle.

The bitmap bitmap area each word (32-bit or 64-bit) corresponds to a 4-bit marker bit. The heapBitsForAddr bitmap bit hbits of the corresponding heap address can be obtained, depending on whether it is a pointer, if it is a pointer and has not been marked before, then mark current object is accessible, and greayObject queued, supplied to other markworker to handle.

// 获取b对应的bitmap位图obj, hbits, span, objIndex := heapBitsForObject(obj, b, i)mbits := span.markBitsForIndex(objIndex)// 判断是否被标记过 已标记或不是指针都不入队mbits.isMarked() hbits.hasPointers(span.elemsize)

Gc_trigger initially starts with a 4mb,next_gc of 4MB, and the dynamic adjustment value is recalculated each time the marker finishes. But the Gc_trigger is at least larger than the initial 4MB, and at least 1MB larger than the currently used heap.

Gcmark resets the threshold size at the end of each marker. 4MB of memory is currently used, when set Gc_trigger to 2*4MB, that is, when memory is allocated to 8MB, the GC is triggered again. After recovering the memory is 5MB, the next time to reach 10MB will not trigger the GC. This ratio triggerratio is determined by gcpercent/100.

func gcinit() {    _ = setGCPercent(readgogc())     memstats.gc_trigger = heapminimum     memstats.next_gc = uint64(float64(memstats.gc_trigger) / (1 +      gcController.triggerRatio) * (1 + float64(gcpercent)/100))     work.startSema = 1    work.markDoneSema = 1}func gcMark() {    memstats.gc_trigger = uint64(float64(memstats.heap_marked) *       (1 + gcController.triggerRatio))}

Forced garbage Collection

If the system is started or a large number of objects are allocated in a short time, the Gc_trigger of garbage collection is pushed higher. When the service is healthy, the active object is much smaller than this threshold, resulting in garbage collection not triggering. The problem was handed over to Sysmon for resolution. It triggers the GC one time every 2 minutes. The FORCEGC Goroutine has been in the background until Sysmon wakes it up and starts executing the GC without checking the thresholds.

// proc.govar forcegcperiod int64 = 2 * 60 * 1e9func init() { go forcegchelper()}func sysmon() {    lastgc := int64(atomic.Load64(&memstats.last_gc))    if gcphase == _GCoff && lastgc != 0 &&        unixnow-lastgc > forcegcperiod &&        atomic.Load(&forcegc.idle) != 0 {            injectglist(forcegc.g)        } }func forcegchelper() {for {    goparkunlock(&forcegc.lock, "force gc (idle)", traceEvGoBlock, 1)    gcStart(gcBackgroundMode, true)    }}

Marking and cleanup process

Here you'll comb the start and flow of the GC from the beginning with the Gc-work section. The following figure summarizes all the state changes in the Mark-sweep. There are only three GC states in the code that correspond to each of these stages. Summarize two questions:

    1. why marktermination need to rescan global pointers and stacks . Because the mark phase is concurrent with the user code, it is possible that new objects are split on the stack, which are recorded by write barrier and checked again at rescan.
    2. Why you need two Stoptheworld in gctermination when you need to STW or there will always be a new object on the stack. Do the preparation before the GC starts (like enable write barrier) STW.

  • OFF:_GCoff
  • Stack Scan + Mark:_GCmark
  • Mark Termination: _gcmarktermination

Goff to Gmark

gcstarttriggered by each MALLOCGC trigger, of course, to meet the threshold conditions such as gc_trriger. The entire boot process is STW, it initiates all the goroutine that will execute the tag work concurrently, and then enters the Gcmark state to write the barrier and start the Gccontroller.

func gcStart(mode gcMode, forceTrigger bool) {    // 启动MarkStartWorkers的goroutine    if mode == gcBackgroundMode {        gcBgMarkStartWorkers()    }    gcResetMarkState()    systemstack(stopTheWorldWithSema)    // 完成之前的清理工作    systemstack(func() {        finishsweep_m()    })      // 进入Mark状态 使能写屏障    if mode == gcBackgroundMode {        gcController.startCycle()        setGCPhase(_GCmark)        gcBgMarkPrepare()        gcMarkRootPrepare()        atomic.Store(&gcBlackenEnabled, 1)        systemstack(startTheWorldWithSema)    }}

Gmark

Explain the relationship between Gcmarkworker and Gccontroller. Gcstart only for all p are ready for the corresponding goroutine to mark. But in the beginning they gopark live g until awakened by Gccontroller findRunnableGCWorker . Goroutine source Record tells the Goroutine process, M start will always find the executable by Schedule G, where Gcworker is also the source of G, but first to check whether the current state is Gmark. If it is, wake the worker and start tagging the work.

func gcBgMarkStartWorkers() {    for _, p := range &allp {        go gcBgMarkWorker(p)        notetsleepg(&work.bgMarkReady, -1)        noteclear(&work.bgMarkReady)    }}func schedule() {  ...//schedule优先唤醒markworkerG 但首先gcBlackenEnabled != 0    if gp == nil && gcBlackenEnabled != 0 {        gp = gcController.findRunnableGCWorker(_g_.m.p.ptr())    }}

Wake up and start entering mark Mark work gcDrain . The Gc-work section tells the process of concurrent tagging, which is not repeated here. In summary, each worker goes to the queue to take the node (the Blackened node), and then handles the current node to see if there are pointers and unmarked objects, and continues to queue the child nodes (ashing nodes) until the queues are empty and can no longer find an object to reach.

func gcBgMarkWorker(_p_ *p) {    notewakeup(&work.bgMarkReady)    for {        gopark(func(g *g, parkp unsafe.Pointer) bool {        }, unsafe.Pointer(park), "GC worker (idle)", traceEvGoBlock, 0)        systemstack(func() {            casgstatus(gp, _Grunning, _Gwaiting)            gcDrain(&_p_.gcw, ...)            casgstatus(gp, _Gwaiting, _Grunning)        })        // 标记完成gcMarkDone()        if incnwait == work.nproc && !gcMarkWorkAvailable(nil) {            gcMarkDone()        }    }}

Gmarktermination

Mark ended up calling after gcMarkDone it was mostly StopTheWorld then entered gcMarkTermination in gcMark . probably did the rescan root area work, but saw the blog said Go1.8 has no longer rescan, the details did not understand, the code looks like it is again scanned again AH.

func gcMarkTermination() {    atomic.Store(&gcBlackenEnabled, 0)    setGCPhase(_GCmarktermination)    casgstatus(gp, _Grunning, _Gwaiting)    gp.waitreason = "garbage collection"    systemstack(func() {        gcMark(startTime)        setGCPhase(_GCoff)        gcSweep(work.mode)    })    casgstatus(gp, _Gwaiting, _Grunning)    systemstack(startTheWorldWithSema)}func gcMark(start_time int64) {    gcMarkRootPrepare()    gchelperstart()    gcDrain(gcw, gcDrainBlock)    gcw.dispose()    // gc结束后重置gc_trigger等阈值    ...}

Gsweep

There are several places where sweep can be triggered, such as the end of a GC tag that triggers gcsweep. If it is a concurrent purge, you need to reclaim the heap area from Gc_trigger to the current active memory and wake up the sweep goroutine in the background.

func gcSweep(mode gcMode) {    lock(&mheap_.lock)    mheap_.sweepgen += 2    mheap_.sweepdone = 0    unlock(&mheap_.lock)    // Background sweep.    ready(sweep.g, 0, true)}// 在runtime初始化时进行gcenablefunc gcenable() {    go bgsweep(c)}func bgsweep(c chan int) {    goparkunlock(&sweep.lock, "GC sweep wait", traceEvGoBlock, 1)    for {        for gosweepone() != ^uintptr(0) {            sweep.nbgsweep++            Gosched()        }        goparkunlock(&sweep.lock, "GC sweep wait", traceEvGoBlock, 1)    }}

That is, when the system is initialized, the Bgsweep Goroutine is opened in the background. This g is also one in the park, wake up after the execution of Gosweepone. the process of Seepone is probably: traverse all the spans to see if its sweepgen need to be checked, if you want to check all the bits of the object in this mspan to see if it needs to be recycled. This process may trigger a recovery of Mspan to Mcentral, which may eventually be recycled into the freelist of Mheap. When memory in Freelist exceeds a certain threshold time, it is returned to the kernel by Sysmon recommendations.

Reference articles

Proposal:eliminate STW Stack re-scanning

Go Note-gc

Garbage collection of go1.5

Go garbage Collection profiling

Related Article

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.