golang垃圾回收机制

垃圾回收机制(GC)

垃圾回收原理

垃圾回收的核心就是标记哪些内存还在使用中——被引用到;哪些内存不再使用了——未被使用。把未被引用的内存回收掉,以供后续内存分配时使用。
请添加图片描述
上图中,内存块1、2、4号位上的内存块已被分配(数字1代表已被分配,0 未分
配)。变量a, b为一指针,指向内存的1、2号位。内存块的4号位曾经被使用过,但现在没有任何对象引用了,就需要被回收掉。
垃圾回收开始时从root对象开始扫描,把root对象引用的内存标记为”被引用”,考虑到内存块中存放的可能是指针,所以还需要递归的进行标记,全部标记完成后,只保留被标记的内存,未被标记的全部标识为未分配即完成了回收。

内存标记(Mark)

请添加图片描述
allocBits记录了每块内存分配情况,而gcmarkBits记录了每块内存标记情况。标记阶段对每块内存进行标记,有对象引用的的内存标记为1(如图中灰色所示),没有引用到的保持默认为0。
allocBits和gcmarkBits数据结构是完全一样的,标记结束就是内存回收,回收时将allocBits指向gcmarkBits,则代表标记过的才是存活的,gcmarkBits则会在下次标记时重新分配内存。

三色标记法*

三色只是为了叙述上方便抽象出来的一种说法,实际上对象并没有颜色之分。这里的三色,对应了垃圾回收过程中对象的三种状态:

  • 灰色:对象还在标记队列中等待
  • 黑色:对象已被标记,gcmarkBits对应的位为1(该对象不会在本次GC中被清
    理)
  • 白色:对象未被标记,gcmarkBits对应的位为0(该对象将会在本此GC中被清
    理)

例如当前内存中A~F一共6个对象,根对象a,b本身为栈上分配的局部变量,根对象a、b分别引用了对象A、B, 而B对象又引用了对象D,则GC开始前各对象的状态如下图所示
请添加图片描述
初始状态下所有对象都是白色的。
接着开始扫描根对象a,b:
请添加图片描述
由于根对象引用了对象A、B,那么A、B变为灰色对象,接下来就开始分析灰色对象,分析A时,A没有引用其他对象很快就转入黑色,B引用了D,则B转入黑色的同时还需要将D转为灰色,进行接下来的分析。如下图所示请添加图片描述
上图中灰色对象只有D,由于D没有引用其他对象,所以D转入黑色。标记过程结
束:
最终,黑色的对象会被保留下来,白色对象会被回收掉。

Stop The World(STW)

对于垃圾回收来说,回收过程中也需要控制住内存的变化,否则回收过程中指针传递会引起内存引用关系变化,如果错误的回收了还在使用的内存,结果将是灾难性的。
Golang中的STW(Stop The World)就是停掉所有的goroutine,专心做垃圾回收,待垃圾回收结束后再恢复goroutine
STW时间的长短直接影响了应用的执行,时间过长对于一些web应用来说是不可接受的,这也是广受诟病的原因之一。
为了缩短STW的时间,Golang不断优化垃圾回收算法,这种情况得到了很大的改善。

垃圾回收优化

写屏障(Write Barrier)

前面说过STW目的是防止GC扫描时内存变化而停掉goroutine,而写屏障就是让goroutine与GC同时运行的手段。虽然写屏障不能完全消除STW,但是可以大大减少STW的时间。
写屏障类似一种开关,在GC的特定时机开启,开启后指针传递时会把指针标记,即本轮不回收,下次GC时再确定。
GC过程中新分配的内存会被立即标记,用的并不是写屏障技术,也即GC过程中分配的内存不会在本轮GC中回收。

辅助GC(Mutatos Assist)

为了防止内存分配过快,在GC执行过程中,如果goroutine需要分配内存,那么这个goroutine会参与一部分GC的工作,即帮助GC做一部分工作,这个机制叫作Mutator Assist。

Go 内存 ballast

什么是 Go ballast,其实很简单就是初始化一个生命周期贯穿整个 Go 应用生命周期的超大 slice。

func main() {
  ballast := make([]byte, 10*1024*1024*1024) // 10G 
  // do something 
  runtime.KeepAlive(ballast)
}

上面的代码就初始化了一个 ballast,利用 runtime.KeepAlive 来保证 ballast 不会被 GC 给回收掉。
利用这个特性,就能保证 GC 在 10G 的一倍时才能被触发,这样就能够比较精准控制 GO GC 的触发时机。
这里你可能有一个疑问,这里初始化一个 10G 的数组,不就占用了 10 G 的物理内存呢? 答案其实是不会的。
这个结果是在 CentOS Linux release 7.9 验证的,我们看到占用的 RSS 真实的物理内存只有 344M,但是 VSZ 虚拟内存确实有 10G 的占用。

垃圾回收触发时机

内存分配量达到阈值触发GC

每次内存分配时都会检查当前内存分配量是否已达到阀值,如果达到阀值则立即启动GC。

			```阈值 = 上次GC内存分配量 * 内存增长率```

内存增长率由环境变量GOGC控制,默认为100,即每当内存扩大一倍时启动GC。

定期触发GC

默认情况下,最长2分钟触发一次GC,这个间隔src/runtime/proc.go:forcegcperiod变量中被声明:

// forcegcperiod is the maximum time in nanoseconds between garbage
// collections. If we go this long without a garbage collection, one
// is forced to run.
//
// This is a variable for testing purposes. It normally doesn't change.
var forcegcperiod int64 = 2 * 60 * 1e9

手动触发

程序代码中也可以使用runtime.GC()来手动触发GC。这主要用于GC性能测试和统计。

GC性能优化

GC性能与对象数量负相关,对象越多GC性能越差,对程序影响越大。
所以GC性能优化的思路之一就是减少对象分配个数,比如对象复用或使用大对象组
合多个小对象等等。
另外,由于内存逃逸现象,有些隐式的内存分配也会产生,也有可能成为GC的负
担。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值