go语言gc状态收集 go的gc对比java 的gc
Go GC 简介
GC 与 mutator 线程并发运行,允许多个 GC 线程并行运行
创新互联从2013年成立,先为凤山等服务建站,凤山等地企业,进行企业商务咨询服务。为凤山企业网站制作PC+手机+微官网三网同步一站式服务解决您的所有建站问题。
GC 是一个使用写屏障的并发标记和清除。
GC 是非分代的,非紧凑的。
Allocation 是按照大小隔离每个 P 分配的区域来完成的,以在消除常见情况下的锁的同时,最小化碎片。
了解 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。
c. 进行内务整理,如 flushing mcaches
4. GC 执行清除阶段
a. 准备清除阶段,将 gcphase 设置为 _GCoff ,设置清除状态并禁用写屏障。
b. Start the world ,从现在开始,新分配的对象是白色的,如有必要,在使用 spans 前 allocating 清除 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 抓取一个新的 span 到 mcache 时, goroutine 会清除 mcache 。
当 goroutine 显式释放对象或设置 finalizer 时,goroutine 确保 span 已经清除(通过清除或者等待并发清除完成)。
finalizer goroutine 仅在所有 span 已经清除时才开始。
当下一次 GC 启动时,它将清除所有尚未清除的 span (如果有的话)。
下一次 GC 是在我们分配了与已经使用的内存成正比的额外内存量之后。
该比例由 GOGC 环境变量控制(默认为 100 )。
如果 GOGC=100 ,而我们使用的是 4M ,那么当达到 8M 时,我们将再次进行 GC(此标记在 next_gc 变量中被跟踪)。
获取 GOGC :
这使得 GC成本 与 allocation 成本 成线性比例。
调整 GOGC 只会改变线性常量(以及使用的额外内存量)。
为了防止在扫描大型对象时出现长时间的暂停,并提高并行性,垃圾收集器将大于 maxObletBytes 的对象的扫描作业分解为最多 maxObletBytes 的 oblets 。
当扫描遇到大对象时,它只扫描第一个 oblet ,并将其余 oblets 作为新的扫描作业排队。
Go 语言三色标记扫描对象是 DFS 还是 BFS?
最近在看左神新书 《Go 语言设计与实现》的垃圾收集器时产生一个疑惑,花了点时间搞清楚了记录一下。
Go 语言垃圾回收的实现使用了标记清除算法,将对象的状态抽象成黑色(活跃对象)、灰色(活跃对象中间状态)、白色(潜在垃圾对象也是所有对象的默认状态)三种,注意没有具体的字段标记颜色。
整个标记过程就是把白色对象标黑的过程:
1.首先将 ROOT 根对象(包括全局变量、goroutine 栈上的对象等)放入到灰色集合
2.选一个灰色对象,标成黑色,将所有可达的子对象放入到灰色集合
3.重复2的步骤,直到灰色集合中为空
下图是书上的插图,看上去是一个典型的深度优先搜索的算法。
下图是刘丹冰写的《Golang 修养之路》的插图,看上去是一个典型的广度优先搜索的算法。
我疑惑的点在于这个标记过程是深度优先算法还是广度优先算法,因为很多文章博客对此都没有很清楚的说明,作为学习者这种细节其实也不影响对整个 GC 流程的理解,但是这种细节我非常喜欢扣:)
对着书和源码摸索着大致找到了一个结果是深度优先。下面看下大致的过程,源码基于1.15.2版本:
gcStart 是 Go 语言三种条件触发 GC 的共同入口
启动后台标记任务
为每个处理器创建用于执行后台标记任务的 Goroutine
上面休眠的 G 会在调度循环中检查并唤醒执行
执行标记
gcw 是每个 P 独有的所以不用担心并发的问题 和 GMP、mcache 一样设计,减少锁竞争
尝试在全局列表中获取一个不为空的 buf
这是官方实现的无锁队列:)涨见识了,for 循环加原子操作实现栈的 pop
到这里从灰色集合中获取待扫描的对象逻辑说完了。找到对象了接着就是 scanobject(b, gcw) 了,里面有两段逻辑要注意
根据索引位置找到对象进行标色
尝试存入 gcwork 的缓存中,或全局队列中
无锁队列,for 循环加原子操作实现栈的 push
到这里把灰色对象标黑就完成了,又放回灰色集合接着扫下一个指针。
Go 语言设计与实现 垃圾收集器
Golang三色标记+混合写屏障GC模式全分析
go的垃圾回收算法
从Gov1.12版本开始,Go使用了非分代的、并发的、基于三色标记清除的垃圾回收器。
关于垃圾回收,比较常见的算法有引用计数、标记清除和分代收集,Golang语言使用的垃圾回收算法是标记清除。
Golang语言的标记清除垃圾回收算法,为了防止GC扫描时内存变化引起的混乱。那么就需要 STW,即Stop The World。具体在Golang语言中是指,在GC时先停止所有goroutine。再进行垃圾回收,等待垃圾回收结束后再恢复所有被停止的goroutine。
标记清除方法
启动STW,暂停程序的业务逻辑,找出不可达对象和可达对象。
将所有可达对象做标记,清除未标记的对象。停止STW,程序继续执行。循环往复,直到进程程序生命周期结束。因为STW需要暂停程序,为了减少暂停程序的时间。将清除操作移出 STW执行周期,但是优化效果不明显。
所谓三色标记,实际上只是为了方便叙述而抽象出来的一种说法,三色对应垃圾回收过程中对象的三种状态。白色是对象未被标记,gcmarkBits对应位为0,该对象将会在本次GC中被清理。灰色是对象还在标记队列中等待被标记,黑色是对象已被标记,gcmarkBits对应位为0,该对象将会在本次 GC中被回收。
分享文章:go语言gc状态收集 go的gc对比java 的gc
转载注明:http://ybzwz.com/article/dodoiip.html