golang 垃圾回收和内存逃逸分析

1. golang垃圾回收

golang的垃圾回收算法是三色标记法,其中三个颜色分别为:灰色、黑色、白色,其对应了垃圾回收过程中变量的三种状态:

  • 灰色:对象还在标记队列中等待
  • 黑色:对象已经被标记,该对象不会在本次GC中被回收
  • 白色:对象为被标记,该对象会在本地GC中被回收

 

1.1 垃圾回收流程

假设现在有这么几个对象:

 

有A-F六个对象,根对象a b为栈区分配的局部变量,根对象a b分别引用了对象 A B,而对象B有引用了对象D

下面简单看一下这几个对象的回收流程:

(1) 初始化:初始化时所有的对象都是白色

(2) 扫描:开始扫描根对象a b,由于a b引用了A B,所以A B设置为灰色

 

(3) 分析:分析对象A,A没有引用其他对象,将A设置为黑色,B引用了D对象,则将B设置为黑色的同时,将D设置为灰色

 

(4)结束:重复步骤3,直到灰色队列为空,这时黑色队列中的对象会被保留,白色队列会被回收

 

 

1.2 GC原理

GC的原理简单来讲就是标记内存中哪些还在使用,哪些不被使用,而不被使用的部分就是GC的对象。

root区域主要是指程序运行到当前时刻的栈和全局数据区域,是正在使用到的内存,当然应该优先标记,而考虑到内存块中可能存放的事指针,所以开需要扫描灰色队列进行递归标记,待灰色队列为空,就可以将白色标记回收

 

1.3 GC优化

golang的垃圾回收算法属于标记-清除,是需要STW的,在golang中就是要停掉所有goroutine,进行垃圾回收,待垃圾回收结束后再恢复goroutine。

所以,STW时间的长短直接影响了应用的执行,为了缩短STW时长,golang优化的GC算法,其中写屏障和辅助GC就是两种优化垃圾回收的方法

写屏障:

  STW的目的是为了防止GC在扫描时出现内存变化而产生混乱,写屏障就是让goutine和GC同时运行,虽然不能完全消除STW,但可以大幅端减少STW时长。

  写屏障在GC的特定时间开启,开启后指针传递时会把指针标记,即本轮不回收,下次GC时再确定。

辅助GC:

  为了防止内存分配过快,在GC执行过程中,GC过程中mutator线程会并发运行,而mutator assisit机制会协助GC做一部分的工作。

 

1.4 GC触发机制

  • 内存分配量达到阈值
  • 定时触发
  • 手动触发:runtime.GC()

 

1.5 GC调优

  • 控制内存分配的速度,限制 Goroutine 的数量,从而提高赋值器对 CPU 的利用率
  • 减少并复用内存,例如使用 sync.Pool 来复用需要频繁创建临时对象,例如提前分配足够的内存来降低多余的拷贝
  • 需要时,增大 GOGC 的值,降低 GC 的运行频率

 

2. 内存逃逸分析

golang中堆栈对于程序员是透明的,栈空间回收更快,堆空间需要触发GC。

逃逸分析,可以尽量把那些不需要分配到堆上的变量直接分配到栈上。

 

2.1 逃逸分析

逃逸分析一个最基本的原则就是:如果一个函数返回对一个变量的引用,那么它就会发生逃逸

逃逸的常见情况:

  • 发送指针的指针或值包含了指针到 channel 中,由于在编译阶段无法确定其作用域与传递的路径,所以一般都会逃逸到堆上分配
  • slices 中的值是指针的指针或包含指针字段
  • slice 由于 append 操作超出其容量,因此会导致 slice 重新分配
  • 调用接口类型的方法
  • 尽管能够符合分配到栈的场景,但是其大小不能够在在编译时候确定的情况,也会分配到堆上

 

2.2 如何避免内存逃逸

  • 减少外部引用, 如指针
  • 应该尽量避免使用接口类型
  • 声明切片的时候,使用make指明初始大小,尽量避免append

 

posted @ 2022-03-21 13:50  aganippe  阅读(358)  评论(0编辑  收藏  举报