GC分类:追踪式、引用计数式

  • 追踪式,分为多种不同类型,例如:
    • 标记清扫:从根对象出发,将确定存活的对象进行标记,并清扫可以回收的对象。
    • 标记整理:为了解决内存碎片问题而提出,在标记过程中,将对象尽可能整理到一块连续的内存上。
    • 增量式:将标记与清扫的过程分批执行,每次执行很小的部分,从而增量的推进垃圾回收,达到近似实时、几乎无停顿的目的。
    • 增量整理:在增量式的基础上,增加对对象的整理过程。
    • 分代式:将对象根据存活时间的长短进行分类,存活时间小于某个值的为年轻代,存活时间大于某个值的为老年代,永远不会参与回收的对象为永久代。并根据分代假设(如果一个对象存活时间不长则倾向于被回收,如果一个对象已经存活很长时间则倾向于存活更长时间)对对象进行回收。
  • 引用数:根据对象自身的引用计数来回收,当引用计数归零时立即回收。

Go中使用的GC方式

Go使用无分代、不整理、并发的*三色标记清除法*(与用户代码并发执行)

Because:(反证法)

  1. 对象整理的优势是为了解决内存碎片化的问题和顺序内存分配器。但Go采用tcmalloc,基本上是没有碎片化的对象。顺序内存分配器在多线程下不适用。基于tcmalloc的内存分配算法,对于整理发来说,并不能得到实质的提升。
  2. 分代回收基于分代假设,分代假设主要将基于回收精力放到新创建的对象上。而Go创建对象时会进行逃逸分析会把对象尽量的分配到栈上,只有长时间存活的对象才会分配到堆上。那么goroutine死亡后,栈也会直接回收,不会参与GC,那么分代回收没有优势。
  3. go采用与用户并发执行,则STW时间与对象的大小和是否分代,并没有直接关系。go团队使用更好的回收方式(部分CPU执行垃圾回收),而不把暂停回收时间作为唯一标准。

三色标记法

  1. 三色标记法是一种描述追踪式垃圾回收的机制的一种方式。它描述了由黑、灰和白色组成(仅为概念),从可能要删除的对象列表中(白色列表),通过_根对象_逐步着色成灰色,最终扫描完成后为黑色,当无灰色对象,其余的白色对象即可回收对象,将被删掉的过程。
  2. 根对象有哪些?
    1. 栈中的对象
    2. 全局变量
    3. 寄存器

并发标记回收方式实现的难点

难点:回收的准确性:*不能漏掉可回收对象,也不能多删不可回收对象*

  1. 赋值器的颜色 及 强弱不变性

    1. 强弱不变性

      当一下两个条件均满足时,会破坏垃圾回收的正确性。(即对象分配为一个有向图

      条件1:赋值器修改对象图,导致黑对象引用白色对象。

      条件2:从灰色对象出发,到达白色对象的、未经访问过的路径被赋值器破坏。

      译:

      条件1:赋值器修改黑色对象引用白色对象。

      条件2:赋值器破坏了未经访问的,灰色对象到白色对象的路径

      当避免条件

      避免条件1:则所有白色对象均被引用,没有白色对象会被遗漏。

      避免条件2:则从灰色对象出发,总存在一条没有访问过的路径,从而找到白色对象,而不会遗漏白色存活对象。

      强不变性:同时满足条件1和条件2。即没有对象会被遗漏,没有未访问的路径会被破坏。
      弱不变性:满足条件1。即没有对象会被破坏。但不能保证黑色对象引用白色对象。
      
    2. 赋值器分为灰色赋值器(Dijkstra插入屏障)和黑色赋值器(Yuasa 屏障

      1.赋值器的颜色对对象回收的影响

      允许灰色赋值器存在

      1. 则必须在回收结束之前重新扫描对象图。
      2. 如果重新扫描过程中,发现了新的灰色或白色对象。回收期还需要对新发现的对象进行追踪。但是在追踪新对象过程中,赋值器仍然可以在根中插入新的非黑色的引用,如此往复,直到重新扫描过程没发现新的白色或灰色对象。·
      3. ·总结·:允许灰色赋值器存在时,最坏情况下,回收期只能将所有的赋值器线程停止才能完成对对象的完整扫描,也就是STW。

      GC触发时机:1.14

      1. 主动触发:阻塞等待完成
      2. 被动触发
        1. 使用系统监控,当超过两分钟没有产生任何GC时强制触发GC。
        2. 使用步调(Pacing)算法,其核心思想是控制内存增长比例。

    GC关注的指标

    1. CPU利用率(因为并发执行)
    2. GC停顿时间
    3. GC停顿频率
    4. GC可扩展性,即回收性能。

    GC调优方向 : 优化内存的申请速度,尽可能的少申请内存,复用已申请的内存

    1. 大量创建Goroutine,对调度器产生很大的压力。可以批量创建部分的goroutine从而降低调度器调度的压力,从而降低GC时间。
    2. 字符串的拼接 + 号,即需要内存拷贝,将内容从原来的内存位置拷贝到新的内存位置。造成大量不必要的内存分配。

GC 触发时间顺序:1.14

参考文献

  1. https://mp.weixin.qq.com/s?__biz=MjM5MDUwNTQwMQ==&mid=2257484062&idx=1&sn=40a8cca15b9c215f8a29e60e6d8a3542&scene=21#wechat_redirect [欧长坤的GC 20问,Tips:就这GC够学一个月了]
  2. https://github.com/golang/proposal/blob/master/design/17503-eliminate-rescan.md [Proposal: Eliminate STW stack re-scanning]
  3. https://github.com/golang/go/issues/17503 [eliminate stack rescanning discussion]