美文网首页
Go GC 简介

Go GC 简介

作者: Leonardyp | 来源:发表于2019-01-23 13:48 被阅读0次

    简单了解

    GC 与 mutator 线程并发运行,允许多个 GC 线程并行运行

    在 GC 的过程中同时运行的 G 称为mutatormutator assist机制就是 G 辅助 GC 做一部分工作的机制。


    辅助 GC 做的工作有两种类型,一种是标记(Mark),另一种是清除(Sweep)。

    GC 是一个使用写屏障的并发标记和清除。

    GC 是非分代的,非紧凑的。

    Allocation 是按照大小隔离每个P分配的区域来完成的,以在消除常见情况下的锁的同时,最小化碎片。

    GC算法的高级描述

    了解 GC 的好地方,可以从 Richard Jones 的gchandbook.org开始。

    1. GC 执行清除终止

          a. Stop the world,这将导致所有 P 达到 GC 安全点。

          b. 清除任何未清除过的spans,只有在预期时间之前强制执行此GC周期时,才会有未清除的span

    2. GC 执行标记阶段

          a.  准备标记阶段,将gcphase设置为_GCmark(从_GCoff开始),启用写屏障,启用mutator assist,并对根标记作业进行排队。

    在所有P都启用写屏障之前,不会扫描任何对象,这是使用STW完成的。

           b. Start the world,从现在开始,GC 工作由调度器启动的标记worker和作allocation的一部分执行的assists来完成。

    写屏障将覆写的指针和任何指针写的新指针值都着色。

    新分配的对象立即被标记为黑色。

          c.  GC 执行根标记作业。包括:扫描所有栈着色所有全局变量,以及着色堆外运行时数据结构中的任何堆指针

    扫描栈会停止goroutine,对goroutine栈中找到的任何指针进行着色,然后恢复goroutine。

           d.  GC 耗尽灰色对象的工作队列,将每个灰色对象扫描为黑色,并对在该对象中找到的所有指针进行着色(反过来可能会将这些指针添加到工作队列中)。

          e.  由于 GC work 分散在本地缓存中,因此 GC 使用分布式终止算法来检测何时不再有根标记作业或灰色对象(参见gcMarkDone函数)。
    此时,GC 状态转换到标记终止(gcMarkTermination)。

    3. GC 执行标记终止gcMarkTermination

          a. Stop the world

          b. gcphase设置为_GCmarktermination,并禁用 workers 和 assists。

    func gcMarkTermination(nextTriggerRatio float64) {
        // World is stopped.
        // Start marktermination which includes enabling the write barrier.
        atomic.Store(&gcBlackenEnabled, 0)
        setGCPhase(_GCmarktermination)
           ......
    }

    //go:nosplit
    func setGCPhase(x uint32) {
        atomic.Store(&gcphase, x)
        writeBarrier.needed = gcphase == _GCmark || gcphase == _GCmarktermination

        //启动写屏障
        writeBarrier.enabled = writeBarrier.needed || writeBarrier.cgo
    }

          c. 进行内务整理,如flushing mcaches

    确保所有的mcaches都被flushed,每个P将在分配之前刷新自己的mcache,但是空闲的P可能不会。


    因为这对于清除所有的span是必要的,所以我们需要确保在下一个 GC 周期开始之前flush完所有mcaches


    mcache:是小对象的每个线程缓存(在Go中是per-P)。


    不需要锁,因为它是每个线程(per-P)。

    mcaches是从非 gc 内存中分配的,因此必须对任何堆指针进行特殊处理。

    type p struct {
        lock mutex

        id          int32
        status      uint32 // one of pidle/prunning/...
        link        puintptr
        schedtick   uint32     // incremented on every scheduler call
        syscalltick uint32     // incremented on every system call
        sysmontick  sysmontick // last tick observed by sysmon
        m           muintptr   // back-link to associated m (nil if idle)
        mcache      *mcache   //一个结构体
    }

    4. GC 执行清除阶段

           a. 准备清除阶段,将gcphase设置为_GCoff,设置清除状态并禁用写屏障。

          b. Start the world,从现在开始,新分配的对象是白色的,如有必要,在使用spansallocating清除spans

           c. GC 在后台进行并发清除并响应allocation,见下面的描述。

    5. 当分配足够时,重复上面 1 开始的步骤,参见下面关于GC rate的讨论。

    并发清除

    清除阶段与正常程序执行并发进行。

    在后台goroutine中,堆被惰性(当 goroutine 需要另一个span时)且并发地逐个span扫描(这有助于不是CPU bound的程序)。

    STW 标记终止的结尾,所有的span都被标记为需要清除
    后台清除器 goroutine 简单地逐个清除span

    为了避免在存在未清除的span时请求更多的OS内存,当goroutine需要另一个span时,它首先尝试通过清除来回收这些内存。

    当 goroutine 需要分配一个新的小对象span时,它会清除相同大小的小对象span,直到释放至少一个对象为止。

    当 goroutine 需要从堆中分配大对象span时,它会清除span,直到将至少那么多页面释放到堆中。

    有一种情况,这可能是不够的:如果 goroutine 清除并释放两个不相邻的单页span到堆中,那么它将分配一个新的双页span,但是仍然可以有其他单页未清除的span,可以组合成双页的span

    确保在未清除的span上不进行任何操作(这会破坏 GC 位图中的标记位)至关重要。
    在 GC 期间,所有mcache都被刷新到中央缓存中,因此它们是空的。

    当一个 goroutine 抓取一个新的spanmcache时,goroutine会清除mcache

    当 goroutine 显式释放对象或设置finalizer时,goroutine 确保span已经清除(通过清除或者等待并发清除完成)。

    finalizer goroutine仅在所有span已经清除时才开始。

    当下一次 GC 启动时,它将清除所有尚未清除的span(如果有的话)。

    GC rate

    下一次 GC 是在我们分配了与已经使用的内存成正比的额外内存量之后。

    该比例由GOGC环境变量控制(默认为100)。

    如果GOGC=100,而我们使用的是4M,那么当达到8M时,我们将再次进行 GC(此标记在next_gc变量中被跟踪)。

    next_gc 在 startCycle函数中被重新计算,即每次gc周期都通过GOGC重新计算下一次gc内存阈值。

    获取GOGC

    func readgogc() int32 {
        p := gogetenv("GOGC")
        if p == "off" {
            return -1
        }
        if n, ok := atoi32(p); ok {
            return n
        }
        return 100
    }

    这使得GC成本allocation 成本成线性比例。

    调整GOGC只会改变线性常量(以及使用的额外内存量)。

    Oblets

    为了防止在扫描大型对象时出现长时间的暂停,并提高并行性,垃圾收集器将大于maxObletBytes的对象的扫描作业分解为最多maxObletBytesoblets

    当扫描遇到大对象时,它只扫描第一个oblet,并将其余oblets作为新的扫描作业排队。

    术语oblets来源:

    obletsTcl语言的一个非常简单的对象系统,对象数据存储在全局数组中。

    由于Tcl对象系统的普及,oblet主要用于教育目的,供那些希望了解简单的基于数组的对象的基本知识的人使用。                 

    TCL经常被用于 快速原型开发,脚本编程,GUI 和 测试 等方面。TCL念作踢叩tickle
    使用最广泛的TCL扩展TK,TK提供了各种 OS 平台下的图形用户界面GUI。连强大的Python语言都不单独提供自己的 GUI,而是提供接口适配到TK上。

    相关文章

      网友评论

          本文标题:Go GC 简介

          本文链接:https://www.haomeiwen.com/subject/zkvjjqtx.html