加入收藏 | 设为首页 | 会员中心 | 我要投稿 江门站长网 (https://www.0750zz.com/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 运营中心 > 建站资源 > 优化 > 正文

关于Golang GC的一些误解,真的比Java算法更领先吗?

发布时间:2019-08-14 15:45:23 所属栏目:优化 来源:William Kennedy
导读:副标题#e# 首先强调下本文的起因是在高可用架构后花园群的一次聊天,大家在争论Golang的GC到底是类似Java的ZGC还是类似Java的CMS GC。我个人的看法是Golang的GC是类似于Java的CMS GC,官方的mgc的注释这么说的: //TheGCrunsconcurrentlywithmutatorthreads

Go垃圾收集器的行为分为两个大阶段Mark(标记)阶段和Sweep(清理)阶段。Mark阶段又分为三个步骤,其中两个阶段会有STW(Stop The World),另一个阶段也会有延迟,从而导致应用程序延迟并降低吞吐量,这三个步骤是:

  • Mark Setup 阶段- STW
  • Marking阶段- 并发执行
  • Mark终止阶段 - STW

下面一一讨论。

Mark Setup阶段

垃圾收集开始时,必须执行的第一个动作是打开写屏障(Write Barrier)。写屏障的目的是允许垃圾收集器在垃圾收集期间维护堆上的数据完整性,因为垃圾收集器和应用程序将并发执行。

为了打开写屏障,必须停止每个goroutine。此动作通常非常快,平均在10到30微秒之内完成。

关于Golang GC的一些误解,真的比Java算法更领先吗?

上图展示了在垃圾收集开始之前有四个goroutine在运行应用程序。为了暂停所有的goroutine,唯一的方法是让垃圾收集器观察并等待每个goroutine进行函数调用。等待函数调用是为了保证goroutine停止时处于安全点。如果其中一个goroutine不进行函数调用而其他goroutine执行函数调用,这种情况下会发生什么?

关于Golang GC的一些误解,真的比Java算法更领先吗?

上图展示了一个问题。在P4上运行的goroutine停止之前,垃圾收集无法启动。然而由于P4处于如下循环中,垃圾收集器可能无法启动。

  1. func add(numbers []int) int { 
  2.     var v int 
  3.     for _, n := range numbers { 
  4.              v += n 
  5.      } 
  6.      return v 

上面的代码片段是P4上正在执行的代码。go routine的运行时间取决于slice的大小。这段代码可以阻止垃圾收集器启动。更糟糕的是,当垃圾收集器等待P4时,其他P也无法提供服务。所以goroutines在合理的时间范围内进行函数调用对于GC来说是至关重要的。

Marking阶段

一旦写屏障打开,垃圾收集器就开始标记阶段。垃圾收集器所做的第一件事是占用25%CPU。垃圾收集器使用Goroutines进行垃圾收集工作,. 这意味着对于一个4线程的Go程序,一个P将专门用于垃圾收集工作。

关于Golang GC的一些误解,真的比Java算法更领先吗?

上图中P1专门用于垃圾收集。现在垃圾收集器可以开始标记阶段。标记阶段需要标记在堆内存中仍然在使用中的值。首先检查所有现goroutine的堆栈,以找到堆内存的根指针。然后收集器必须从那些根指针遍历堆内存图,标记可以回收的内存(译者注:标记的算法就是所谓的三色标记算法)。当标记工作在P1上进行时,应用程序可以在P2,P3和P4上继续进行。这意味着垃圾收集器的影响已最小化到当前CPU的25%。

这是理想的情况,然而现实却远没有如此简单。如果在垃圾收集过程中,P1在堆内存达到极限之前无法完成标记工作(因为应用程序可能在大量分配内存),该怎么办?如果3个Goroutines中只有一个大量分配内存导致P1无法完成标记工作,在这种情况下,分配新内存的速度会变慢,特别是始作俑者的那个Go routine分配内存的时候。

如果垃圾收集器确定需要减慢内存分配,原本运行应用程序Goroutines会协助标记工作。应用程序Goroutine成为Mark Assist(协助标记)中的时间长度与它申请的堆内存成正比。Mark Assist有助于更快地完成垃圾收集。

关于Golang GC的一些误解,真的比Java算法更领先吗?

上图显示了在P3上运行的应用程序Goroutine现在正在执行Mark Assist并进行收集工作。

垃圾收集器的一个设计目标是减少对Mark Assists的需求。如果任何本次垃圾回收最终需要大量的Mark Assist才能完成工作,则垃圾收集器会提前开始下一个垃圾收集周期。这样做可以减少下一次垃圾收集所需的Mark Assist。

Mark终止

一旦并发标记阶段完成,下一个阶段就是标记终止。最终关闭写屏障,执行各种清理任务,并计算下一个垃圾回收周期的目标。一直处于循环中的goroutine也可能导致stw延长(类似mark setup的情况)。

关于Golang GC的一些误解,真的比Java算法更领先吗?

上图显示了在标记终止阶段完成时如何停止所有goroutine。这一动作平均在60到90微秒之间完成。这个阶段可以在没有STW的情况下完成,但是使用STW的代码更简单。

一旦收集完成,应用程序Goroutines就可以再次使用所有P,应用程序将恢复到油门全开的状态。

关于Golang GC的一些误解,真的比Java算法更领先吗?

上图显示了垃圾收集完成后,所有P现在都可以用于应用程序。

并发清理

(编辑:江门站长网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!