go

go随笔-垃圾回收

Posted by YaPi on September 10, 2021

垃圾回收

垃圾分类

语义垃圾(semantic garbage)—有的被称作内存泄露,语义垃圾指的是从语法上可达(可以通过局部、全局变量引用 得到)的对象,但从语义上来讲他们是垃圾,垃圾回收器对此 无能为力。

语法垃圾(syntactic garbage) 语法垃圾是讲那些从语法上无法到达的对象,这些才是垃圾收集器主要的收集目标。

常见垃圾收集算法

引用计数(Reference Counting): 某个对象的根引用计数变为 0 时,其所有子节点均需被回收。 标记压缩(Mark-Compact): 将存活对象移动到一起,解决内存碎片问题。 复制算法(Copying): 将所有正在使用的对象从 From 复制到 To 空间,堆利用率只有一半。 标记清扫(Mark-Sweep):解决不了内存碎片问题。需要与能尽量避免内存碎片的分配器使用,如 tcmalloc。Go语言也是采用的此方式

GC标记流程

avatar

触发 gcStart
  • runtime.GC : 手动调用
  • runtime.mallocgc,可能内存分配的速度会超过内存回收的速度,所以在内存分配的时候可能会触发GC或协助处理一些GC的工作
  • forcegchelper: 若在一定时间内没有执行GC,Go后台会启动一个协程执行GC。一般时间为2min

avatar

大致流程
  1. 三种GC触发方式,触发gcStart()
 1.1 抢占全局锁,因为同一时间可能会多次触发gcStart(),比如:同一时间多次分配内存
 1.2 调用gcBgMarkStartWorkers(),开启后台GC的worker,这个G和P进行绑定,执行标记等任务。
     但是不是立即执行,会先启动协程,在调度器执行GC时唤醒(schedule() -> gcBlackenEnabled !=0 -> findRunnableGCWorker()),根据当前P的数量,唤醒一定比例(0.25*P)的worker
  1. 根据ROOT对象遍历(此过程在调用器唤醒过后才会执行)。在Go语言中,根对象包括了全局变量(在.bss 和.data段内存中)、span中的finalizer的任务数量、以及所有的协程栈。 finalizer是Go语言中和某种对象绑定的析构器。当某些对象的内存释放后,需要调用析构器函数,从而完整释放资源。例如os.File对象使用了析构器函数关闭操作系统文件描述符,即便用户忘记了调用close()方法也会释放操作系统资源。
2.1 gcMarkWorker(每个P的GC标记的对象)和Mark assist(内存分配的时候,辅助GC标记的对象)会存放于P的gcw内
    gcw是由两个worker数组wbuf1、wbuf2构成,数组长度大概为253。每次将扫描到的对象添加到gcw时,会使用其中到一个wbuf1,
    当其中一个满了的时候,会将两个buf交换。当两个都满了的话,会将wbuf2推送到全局worker数组(work.full)。在标记的过程
    中,也可能会碰到本地wbfu1、wbfu2没有值的情况,这时会从全局work.full中偷取一部分指针数据放到本地的gcw当中,如果还没
    有数据,就会从wbBuf(开启写屏障过后的数据)当中取
2.2 在标记过程中,开启了写屏障过后,在此期间的指针修改情况会放在P的wbBuf当中,这是一个512大小的数组
三色标记
三色抽象
黑:已经扫描完毕,子节点扫描完毕。(gcmarkbits = 1,且在队列外)
灰:已经扫描完毕,子节点未扫描完毕。(gcmarkbits = 1, 在队列内)
白:未扫描,collector 不知道任何相关信息

三色标记过程中并未STW,所以业务代码可能会修改指针的指向。为了解决此为题需要在垃圾回收过程中开启写屏障。
三色标记过程中,解决对象漏标的情况有两种理论基础,强、弱三色不变性

强三色不变性:禁止黑色对象指向白色对象
弱三色不变性:黑色对象可以指向白色对象,但同时必须有灰色对象到它的可达路径

两种算法:

Dijkstra barrier: 如果目标对象src为黑色,则将新引用的对象标记为灰色
Yuasa barrier: 一旦取消了原引用后,就立即将原引用标记为灰色。

这两种算法他们都存储浮动垃圾的问题,所以go语言中采用了混合屏障的算法

即,混合屏障会将指针(指向堆的)修改前的位置和修改后的指向的位置都标灰

同时,在垃圾收集过程中分配的对象,都标记为黑色
代码流程
  • gcStart -> gcBgMarkWorker && gcRootPrepare,这时 gcBgMarkWorker 在休眠中
  • schedule -> findRunnableGCWorker 唤醒适宜数量的 gcBgMarkWorker
  • gcBgMarkWorker -> gcDrain -> scanobject -> greyobject(set mark bit and put to gcw)
  • 在 gcBgMarkWorker 中调用gcMarkDone排空各种wbBuf后,使用分布式termination检查算法,进入gcMarkTermination -> gcSweep 唤醒后台沉睡的 sweepg 和 scvg -> sweep -> wake bgsweep && bgscavenge
垃圾收集过程中CPU使用控制
  • GC 的 CPU 控制目标是整体 25%
  • 当P=4*N时,只要启动N个worker就可以。
  • 但 P 无法被 4 整除时,需要兼职的 gcMarkWorker 来帮助做一部分工作
  • 全职 GC 员工:Dedicated worker,需要一直干活,直到被抢占。
  • 兼职 GC 员工:Fractional worker,达到业绩目标(fractionalUtilizationGoal)时可以主动让出。
  • 还有一种 IDLE 模式,在调度循环中发现找不到可执行的 g,但此时有标记任务未完 成,就用开启 IDLE 模式去帮忙。
  • Worker 运行模式在:p.gcMarkWorkerMode

相关参数

  • https://jishuin.proginn.com/p/763bfbd31e38
  • https://jishuin.proginn.com/p/763bfbd31e32
  • https://jishuin.proginn.com/p/763bfbd323e4
  • https://jishuin.proginn.com/p/763bfbd4ea8e
  • https://jishuin.proginn.com/p/763bfbd4ea8b
  • https://jishuin.proginn.com/p/763bfbd4ea86
  • https://jishuin.proginn.com/p/763bfbd4ea84
  • https://jishuin.proginn.com/p/763bfbd4ec55
  • https://jishuin.proginn.com/p/763bfbd4ee7b
  • https://jishuin.proginn.com/p/763bfbd54fed