当前位置: 首页 > 科技观察

GoGC是如何标记内存的?颜色是什么意思?

时间:2023-03-12 04:58:03 科技观察

为“AJourneyWithGo”创作的插图,由原版GoGopher制作,由ReneeFrench创作本文基于Go1.13。我的文章MemorymanagementandallocationinGo[1]中详细解释了内存管理概念的讨论。GoGC的作用是回收不再使用的内存。实现的算法是并发三色标记和扫描回收方法。在这篇文章中,我们将研究三色符号和每种颜色的不同用途。您可以在KenFox的InterpretingGarbageCollectionAlgorithms[2]中阅读更多关于不同垃圾收集机制的信息。标记阶段此阶段扫描内存以了解我们的代码正在使用哪些块以及应该回收哪些块。但是,由于GC与我们的Go程序并行运行,内存中某些对象的状态可能会在GC扫描期间发生变化,因此需要一种检测这种可能变化的方法。为了解决这个潜在的问题,实现了写屏障[3]算法,GC可以跟踪任何指针修改。写屏障生效的唯一条件是程序的短暂终止,也就是“停止世界”。在进程启动时,Go还会为每个处理器设置一个标记工作器,协助标记内存。然后,当根入队处理时,标记阶段开始遍历并对内存进行颜色标记。为了理解标记阶段的每一步,让我们看一个简单的程序示例:)func(){_=allocStruct2()}()runtime.GC()fmt.Printf("s1=%X,s2=%X\n",&s1,&s2)}//go:noinlinefuncallocStruct1()*struct1{return&struct1{e:allocStruct2(),}}//go:noinlinefuncallocStruct2()*struct2{return&struct2{}}struct2不包含指针,所以它被存储在一个专用于不被其他对象引用的对象的span中。不包含指针的结构存储在专用跨度中,这减少了GC的工作,因为在标记内存时不需要扫描跨度。分配完成后,我们的程序会强制GC重复前面的步骤。下面是流程图:scanmemoryGC从栈开始,递归跟随指针找到指针指向的对象,遍历内存。当扫描标记为未扫描的跨度时停止扫描。然而,这项工作是在多个协程中完成的,每个指针都在一个工作池中排队。然后,在后台运行的markerworker从工作池中获取之前出队的工作,扫描对象并将在对象中找到的指针添加到队列中。垃圾收集器工作池颜色标记工作者需要一种方法来跟踪需要扫描的内存。GC使用三色标记算法[4]并按如下方式工作:最初,所有对象都被视为白色根对象(堆栈、堆、全局变量)被标记为灰色在完成此初始步骤后,GC将:选择Agrayobject,markedasblack跟踪该对象的所有指针,将所有引用的对象标记为gray然后,GC重复以上两个步骤,直到没有对象可以被标记。此时,物体不是黑就是白,没有灰色。白色的对象表示没有其他对象引用,可以回收。这是前面示例的图形表示:最初,所有对象都被认为是白色的。然后,被其他对象遍历和引用的对象被标记为灰色。如果对象在标记为未扫描的范围内,则可以将其标记为黑色,因为它不需要扫描。现在灰色对象被添加到扫描队列并标记为黑色:对所有添加到扫描队列的对象重复相同的操作,直到没有对象需要处理:处理结束时,黑色对象表示内存中正在使用的对象,白色对象是要回收的对象。我们可以看到,由于struct2的实例是在匿名函数中创建的,栈上已经不存在了,所以是白色的,可以回收。由于在每个span中都有一个名为gcmarkBits的位图属性,因此本机实现了三色,位图通过将扫描中的相应位设置为1来跟踪扫描。我们可以看到黑色和灰色表示相同的东西。处理上的区别是对象标记为灰色时加入扫描队列,标记为黑色时不进行扫描。GC最后STW,清除每个writebarrier对工作池所做的改变,继续后续的标记。您可以在我的文章HowGoGCcanmonitoryourapplication[5]中找到关于并发处理和GC标记阶段的更详细描述。runtimeprofilerGo提供的工具让我们一步一步可视化,观察GC对我们程序的影响。在启用跟踪的情况下运行我们的代码,以查看前面所有步骤的概览。以下是痕迹:垃圾收集器标记工作人员生命周期的痕迹也可以在痕迹中的协程级别可视化。下面是一个Goroutine#33的例子,它在后台等待启动前标记内存。标记工人