Go语言垃圾回收(GC原理)

1. GC回收机制

1.1 V1.3标记清除法

(1)概述

1.STW暂停

STW(暂停业务逻辑,找出可达和不可达对象)

2.对可达对象做上标记

标记完成之后,对象5和对象6不可达,被GC清除.之后STW结束.

(2).缺点
  • STW :让程序暂停,程序出现卡顿.
  • 标记需要扫描整个heap.
  • 清除数据会产生heap碎片.

1.2 V1.5三色标记法

(1) 概述

1.程序创建起初,全部标记为白色,将所有对象放入白色集合中.

2. 将程序的根节点集合展开,遍历Root Set(非递归形式,只遍历一次).得到灰色节点

3.遍历灰色标记表,将可达的对象从白色标记为灰色,遍历之后的灰色,标记为黑色.

4.循环执行第三步,直到灰色标记标中无任何对象.

5.收集所有白色对象(垃圾)

(2) 缺点

如果三色标记法不被STW保护.

当一个白色对象被黑色对象所引用,且灰色对象与它之间的可达关系的白色对象遭到破坏,就会出现对象丢失的情况.

避免上述情况最简单的方式就是STW.

尽量减少STW对程序性能的损耗

  • 强三色不变式: 强制行的不允许黑色对象引用白色对象.
  • 弱三色不变式: 黑色可以引用白色对象,白色对象存在其它灰色对象对它的引用,或者可达它的链路上游存在灰色对象.

屏障机制实现强弱三色不变式

  • 插入屏障: 在A对象引用B对象的时候,B对象被标记为灰色.(将B挂在A下游,B必须被标记为灰色) 满足强三色不变式(不存在黑色对象引用白色对象的情况了,因为白色会强制变成灰色) 插入屏障不在栈上使用.
添加一个下游对象(当前下游对象slot,新下游对象ptr) {
    //1
    标记灰色(新下游对象ptr)
    //2
    当前下游对象slot=新下游对象ptr
}

插入屏障流程:

由于并发特性,此刻外界向对象4添加添加对象8,对象1添加对象9.对象4在堆区,即将触发插入屏障机制.

当灰色标记表中为空,准备回收白色标记之前,重新遍历扫描一次栈空间.此时加STW暂停保护栈,防止外界干扰.(这也是插入写屏障的不足,结束时需要STW重新扫描栈,10~100ms).

  • 删除屏障: 被删除的对象,如果自身为灰色或者白色,那么被标记为灰色. 满足弱三色不变式(保护灰色对象到白色对象的路径不会断)
添加下游对象(当前下游对象slot , 新下游对象ptr) {
    //1
    if 当前下游对象slot是灰色 || 白色{
        标记灰色(当前下游对象slot)
    }
    //2
    当前下游对象slot = 新下游对象ptr
}

删除屏障流程:

灰色对象1删除对象5,如果不触发删除写屏障,5-2-3路径与主链路断开,最后均会被当作垃圾清除.

删除写屏障回收精度低,一个对象即使被删除了最后一个指向它的指针,也依旧可以活过这一轮,在下一轮的GC中被清理掉.

1.3 V1.8混合写屏障机制

(1) 概述

具体操作:

  1. GC开始将栈上的对象全部扫描并标记为黑色(之后不再进行第二次重复扫描,无需STW)
  2. GC期间,任何在栈上创建的新对象,均为黑色.
  3. 被删除的对象标记为灰色.
  4. 被添加的对象标记为灰色.

混合写屏障流程:

1.GG刚开始,默认都为白色.

2.三色标记法,优先扫描全部栈对象,将可达对象均标记为黑.

2. GPM调度模型设计

2.1. GPM

  1. G(goroutine协程):Go协程
  2. P(Processor处理器):包含运行Go代码的必要资源,也有调度go协程的能力
  3. M(machine线程):工作线程,由操作系统调度

P的本地队列存放的是等待运行的G,数量不能超过256G,优先将新创建的G放在P的本地队列中,如果满了会放在全局队列中.

GOMAXPROCS:是在程序启动时创建的,指定P的个数.可以通过环境变量,或者在程序中通过runtime.GOMAXPROCS()来设置.(允许相应个数的go协程在同一时刻并行执行)

Go语言本身限制M的最大量为10000.一般M的个数会略大于P.

2.2. 调度器设计策略

  1. 复用线程:避免频繁的创建,销毁线程.而是对线程复用.
    1. work stealing机制: 当本线程无可运行的G时,尝试从其他线程绑定的P偷取G,而不是销毁线程.
    2. hand off机制: 当本线程因为G进行调用阻塞时,线程将释放绑定的P,将P转移给其他空闲线程执行.
  1. 利用并行: GOMAXPROCS设置P的数量,最多由GOMAXPROCS个线程分布在多个CPU上同时运行.
  2. 抢占:在Go中,一个goroutine最多占用CPU10ms , 防止其他goroutine长期得不到执行.
  3. 全局G队列:当M执行work stealing从其他P中偷不到G时,它可以从全局G队列获取G.

2.3. go func()

  1. 有两个存储G的队列,一个是局部调度器P的本地队列,一个全局队列.新创建的G会先保存在P的本地队列中,如果P的本地队列中已满就会保存在全局的队列中.
  2. G只能运行在M中,一个M必须持有一个P.M会从P的本地队列弹出一个可执行状态的G来执行,如果P的本地队列为空,偷取其他队列的P来执行 .
  3. 一个M调度G执行的过程是一个循环机制.
  4. 当M执行某一个G时候如果发生了syscall或其余阻塞操作,M会阻塞,如果当前有一些G在执行,runtime会把这个线程M从P中摘除,然后再创建一个新的操作系统的线程(如果有空闲的线程可用就复用空闲线程)来服务这个P.
  5. 当M系统调用结束后,这个G会尝试获取一个空闲的P执行;并放入到这个P的本地队列.如果获取不到P,那么这个线程M变成休眠状态,加入到空闲线程中,然后这个G会被放入全局队列中.

2.4. 调度器的生命周期

M0:启动程序后的编号为0的主线程.在全局变量runtime.m0中,不需要再heap上分配.负责执行初始化操作和启动第一个G.启动第一个G之后,M0就和其他M一样了.

G0:每次启动一个M,第一个创建的gourtine,仅用于负责调度的G,不指向任何可执行的函数.在调度或系统调用时,会使用M切换到G0来调度.

2.5. 补充

自旋线程会不断的寻找G(优先寻找全局队列中的G)

从全局队列到P本地队列的负载均衡:从全局队列取的个数: n = min(len(GQ) / GOMAXPROCS + 1 , len(GQ)/2)

GQ:全局队列的总长度

偷取另一个P本地队列中的G: 将要偷取的P中的本地队列一分为二,偷取后一半的G.

自旋线程的最大限制: 自旋线程+执行线程 <= GOMAXPROCS

  • 30
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值