Go GC 简介

简单了解

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上。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容