(golang需要开发栈pdf)(golang适合初学者项目源代码)

课程链接:https://www.itwangzi.cn/4590.html

(golang需要开发栈pdf)(golang适合初学者项目源代码)

对于 Go 中的垃圾收集,它总是不太熟悉。因此要具体分析,具体流程。本次探索的 Go 版本go version go1.13.15 darwin/amd64

1

什么是GC

垃圾回收(GARBAGE COLLECTION,GC)是编程语言中提供的一种自动内存管理机制。自动释放不需要的对象,释放内存资源,不需要程序员手动执行。

当程序不再需要向操作系统申请时,垃圾回收器主动回收并多路复用用于内存申请,或者返回给操作系统,这是内存级别资源的自动回收过程,即Recycling for Garbage . 负责垃圾收集的程序组件是垃圾收集器。 2
Go中的GC方法所有的 GC 算法都有可以归因于跟踪和引用计数(Reference Counting)的存在形式。

  • 追踪(trace):从根对象开始,根据对象之间的引用信息,一步一步直到整个堆扫描完成,并确定需要保留的对象,从而回收所有可回收的对象。GO、Java、V8 的 JavaScript 实现就是跟踪 GC。
  • 引用计数:每个对象本身都包含一个引用的计数器,当计数器归零时,该计数器会自动接收回收。由于这种方法缺陷较多,所以在追求高性能时通常不会应用。Python,Objective-C都是引用计数GC。

目前常见的 GC 实现包括:跟踪(trace)

  • 标记清理:从根对象开始,确定要标记的对象,以及可以回收的对象;
  • 标记整理:为了解决内部存在块的问题,在标记过程中,对象会尽可能的整理到一个连续的内存中;
  • 增量标记清理:执行标记和清理过程,每次执行一小部分,从而递增垃圾收集,达到近似实时,几乎没有停止;
  • 增量标记整理:在增量的基础上,增加对象的整理工序;
  • 最后:根据对象的存活时间长短分类,存活时间小于一定值,存活时间大于一定值,即永远不会参与回收的对象是永久的。并且根据假设(如果一个对象存活时间不长,则倾向于恢复如果一个对象存活了很长时间,则倾向于存活对象恢复对象;

引用计数

  • 引用计数:根据对象本身的引用计数进行回收,当引用计数返回时立即回收;

Go目前使用的是unparalleled(object没有intergeneration),没有组织三色标记清理算法(recovery期间不针对object),concurrent(with user)的三色标签清洗算法同时编码)。原因:1. 对象整理的好处是解决内部停滞和“允许”使用顺序内存分配器。不过GO运行时分发算法是基于TCmalloc的,基本没有分片。并且顺序内存分配器不适用于多线程场景。go 被基于 TCmalloc 的现代内存分配算法使用,对象没有被组织以提高实质性性能。2、GC依赖假设,即GC将主要回收目标放在新创建的对象上(存活时间短,更容易被回收),而不是频繁检查所有对象。但是,Go 的编译器会通过逃逸分析将大部分新对象存储在堆栈中(堆栈直接回收),只有那些需要长期存在的对象才会被分配到需要垃圾回收的地方。即 GC 回收的 Survival time 直接分配给 Go 中的栈。当 Goroutine 死栈会直接回收,不参与 GC,没有带直接优势。并且GO的垃圾收集器是和用户代码同时实现的,所以STW的时间和IM的Identity,object的Size没有关系。

Go中使用三色标记法

3
三色标记法三色标记,从字面上我们可以知道它由三种颜色组成:恢复通过将对象映射分为三个状态来指示其扫描过程。白色对象白色:潜在的垃圾,它的内存可能会被垃圾收集器回收。如果扫描完成,对象还是白色的,说明这个对象是垃圾对象。灰色对象灰色:活动对象,因为有一个指向白色对象的外部指针,垃圾收集器会扫描这些对象的子对象;黑色对象黑色:活动对象,包括没有任何引用外部指针的对象和来自根对象的对象;三色标记规则:黑色不能指向白色物体。即黑色可以指向灰色,灰色可以指向白色。(golang需要开发栈pdf)(golang适合初学者项目源代码)

根对象

在介绍三色标记法之前,首先要了解什么是根对象。根对象在垃圾回收的术语中称为根集合,是垃圾回收器在标注过程中的主体,包括:全局变量:程序可以在编译期确定,存在于程序的整个生命周期中。
执行堆栈:每个 Goroutine 都包含自己的执行堆栈,其中包含堆栈上的变量和指向已分配堆栈内存块的指针。
寄存器:寄存器的值可能代表一个指针,这些参与计算的指针可能指向某个赋值分配的堆内存块。“根对象”首先需要在 GC 的标记阶段进行标记,所有从根对象开始可达的对象都将被视为存活。
根对象包含全局变量、每个G的栈上的变量等,GC会首先扫描所有能够再次到达根对象的对象。当垃圾回收器中没有黑色对象时,程序中没有黑色对象,垃圾回收的根对象被标记为灰色,垃圾回收器只会从灰色对象集合中取出该对象,并有在灰色集合中没有对象时,标记阶段将结束。(golang需要开发栈pdf)(golang适合初学者项目源代码)
三色标记垃圾收集器的工作原理非常简单,我们可以总结为以下几个步骤:1、从灰色对象集合中选择一个灰色对象,标记为黑色;
2、将所有指向黑色对象的对象标记为灰色,保证对象和对象引用的对象不会被回收;
3、重复以上两步,直到物体图中没有灰色对象;三色标记结束后,只剩下白色和黑色,最后恢复成白色对象。 4
SWT由于用户程序在标记执行过程中可能会修改对象的指针,因此三色标签清除算法本身在并发或增量执行中是不可能的。(golang需要开发栈pdf)(golang适合初学者项目源代码)
例如,在三色标记期间,用户程序建立了从A对象到D对象的引用,但由于程序中没有灰色对象,D对象会被垃圾回收器错误地回收。不应该回收的对象被回收了,这给我们的程序带来不可预知的问题。什么是 STW?STW是StopTheWorld的缩写,意思是有一个进一步操作对象映射的过程,以保证垃圾回收过程中实现的正确性,保证内存无限增长。STW的进程存在明显的资源浪费,对所有用户程序都有很大的影响。早期的 GO 在垃圾回收器的实现有几百毫秒,虽然 STW 现在已经优化到了秒级的一半,但是 STW 的效果仍然存在。想要并发或增量标记对象需要使用屏障技术。5
屏障技术Golang中使用的垃圾回收,即多次赋值和回收,并应用了barrier技术来保证回收的正确性。原理主要是以上两种情况之一。内存屏障技术是一种屏障命令,允许 CPU 或编译器在执行与内存相关的操作时遵循特定的约束。大多数现代处理器将执行指令以最大限度地提高性能,但这种技术可以保证代码。内存操作的顺序性,在内存屏障之前执行的操作必须在内存屏障之前执行。我们要保证并发或增量标记算法的正确性,我们需要实现以下两个三色不变量中的任意一个:三色不变式:弱三色不变式黑色对象引用的所有白色对象都处于灰色保护状态(直接或间接来自灰色对象)。强三色不变:没有黑色对象指向白色对象的指针。强三色不变式黑色物体不指向白色物体,只会指向灰色物体或黑色物体;强三色不变性很好理解,强制不允许黑色对象引用白色对象。在弱三色不变量中,黑色对象可以引用白色对象,但是这个白色对象仍然存在其他灰色对象对其引用,或者可以到达上游到其链接的灰色对象。
1、插入屏障
Dijkstra 在 1978 年提出插入一个写屏障,通过下图所示的写屏障,用户程序和垃圾收集器可以在交替工作的情况下保证程序的正确性:

writePointer(slot, ptr): shade(ptr) *slot = ptr在黑色对象中插入屏障拦截白色指针,将其对应对象标记为灰色状态,这样就没有黑色对象引用白色对象,满足强三色不变量。比如黑色A指向白色D,A引用D时可以直接将D标记为灰色。(golang需要开发栈pdf)(golang适合初学者项目源代码)
1.垃圾收集器将A对象标记为黑色,然后指向该对象的B对象标记为灰色;
2、用户程序修改对象的指针,指向B对象的指针,指向C对象,触发写屏障,根据强三色不变量,黑不能直接指向白改C对象的颜色为灰色;
3、垃圾收集器依次遍历程序中的其他灰色对象,将其标记为黑色;Dijkstra的写屏障非常简单,保证了强三色不变性,但它也有一个明显的缺点。因为栈上的对象也会被认为是垃圾回收中的根对象,所以为了保证内存的安全,必须在栈上添加写屏障或者在标记阶段扫描整个栈上的对象。该方法有各种缺点,前者会大大增加写指针的额外开销,而后者需要在重新筛选堆栈对象时暂停程序。在golang中,栈上写屏障的写入是非常高的,所以Go选择只插入指向堆的指针来增加写屏障,这样扫描完成后,栈上还有一个引用的白色对象。这种情况,栈是灰色的,不满足三色不变量,所以需要重新扫描栈使其变黑,完成剩余对象的标记,而这个过程需要STW。在应用程序比较大时,STW的时间可能会达到 10 到 100 毫秒。2、删除屏障YUASA 在 1990 年的论文 Real-Time Garbage Collection on General-Purpose Machines 中提出了 delete write barrier,因为一旦 write barrier 开始工作,就会保证 write barrier 上的所有对象都到达,所以也是 Snapshot GC)称为快照 gc。

writePointer(slot, ptr): if (isGery(slot) || isWhite(slot)) shade(*slot) *slot = ptr删除屏障也拦截了写操作,如果遵循三色不变的话,会保护灰色对象到白色对象的路径不被破坏。(golang需要开发栈pdf)(golang适合初学者项目源代码)

  1. 先将对象A标记为黑色,再将对象A指向的对象B变灰;
  2. 程序将对象A指向对象C,触发删除屏障,取决于是否三色不变,因为对象C有灰色对象B的引用,所以不用改;
  3. 然后程序将对象B删除对象C的引用,触发删除屏障,因为对象D不存在直接和间接的灰色对象引用,因此需要将对象C的颜色改为灰色;
  4. 垃圾收集器依次遍历,标记GC。

上述第2步违反了强三色不变量,黑色物体直接指向白色物体。下一步3违反弱三色不变量,对象D没有直的或间接的灰色对象。通过重载C,保证C和D对象的安全。

混合写屏障

插入写屏障和删除写屏障:插入写屏障:最后需要STW,并且栈需要重新扫描,以便新被引用的白色对象得以存活;删除写屏障:低恢复,GC开始时STW扫描栈记录初始快照,保护开始时所有存活对象。在 Go 1.7版本之前,采用插入写屏障以确保强三色不变性,但它并不是运行在所有垃圾收集根对象上。因为 Go 语言应用程序可能包含数百个 Goroutine,垃圾回收的根对象一般包括全局变量和栈对象,如果运行在数百个 Goroutine 的栈上,会带来巨大的额外开销,所以 Go 团队在tag 阶段完成,将所有堆栈对象标记为灰色并重新扫描,在活动的 Goroutine 中,
重新扫描的过程需要占用 10 到 100 ms。具体操作:

  1. GC开始扫描堆栈上的所有对象,并将其标记为黑色(没有第二次重复扫描,没有STW);
  2. GC,在栈上创建的任何新对象都是黑色的;
  3. 被删除的对象被标记为灰色;
  4. 添加的对象标记为灰色;


伪代码

writePointer(slot, ptr): shade(*slot) if current stack is grey: shade(ptr) *slot = ptr 6
GC处理过程1、准备阶段:STW,初始化标签任务,启用写屏障2、标记阶段gcmark与内存分配并发执行,写屏障开启1、切换状态为_gcmark,开启写屏障,用户程序辅助(Mutator Assiste)进入根对象;
2、恢复正在执行的程序、标签进程、用户程序进行协助,会开始兼容内存中的对象,写屏障会将被覆盖的指针和新的指针标记为灰色,所有新创建的对象直接标记为黑色;
3、开始扫描根对象,包括所有的Goroutine栈、全局对象、栈中运行的数据结构,在Goroutine Stack期间扫描当前处理器;
4 处理灰色队列中的对象,将对象标记为黑色,将对象点标记为灰色;
5、使用分布式终止算法检查剩余工作,在标签阶段完成后找到标签终止阶段;3、标签终止阶段STW,保证标记任务在一个周期内完成,停止写障碍4、清理阶段并发执行1.将状态切换为_gcoff开始清理阶段,初始化清理状态并关闭写屏障;
2、恢复用户程序,所有新创建的对象都可以用白色标记;
3、后台并发清理所有内存管理单元,当Goroutine申请新的内存管理单元时触发清理; 7
GC触发时间Go语言中GC的触发时间有两种形式:

  • 主动触发,通过调用runtime.gc触发GC,调用阻塞等待当前GC操作。
  • 被动触发,分为两种方式:
    1、使用系统监控,当超过两分钟没有产生GC时,强制触发GC。
    2、采用Pacing算法,其核心思想是控制内存增长的比例。

例如:

  • 内存大小阈值,内存到达上次GC前的2倍
  • 时间,2M 间隔

阈值由一个 GC Percent 的变量控制,当新分配的内存超过使用中的内存比例超过 GCPRECENT 时触发。比如恢复完成后,内存量是5m,那么下一次recovery就是给10m分配内存。也就是不是内存分配,垃圾回收频率越高。如果你没有达到内存大小的阈值?这时候会定时触发GC。比如还没到10m,那就是时间(默认2min触发一次)触发GC保障资源的回收。

如果内存分配速度超过标签清除速度怎么办?

如果后台执行的垃圾收集器不够快,应用程序申请内存的速度超出预期。运行时,允许应用程序协助内存应用程序完成垃圾回收的扫描阶段,并在标签和标签终止阶段后进入异步。清理阶段将在没有内存增量的情况下恢复。并发标记设置一个标志并检查 Mallocgc 何时调用。当有新的内存分配时,那些 GOROUTINE 会暂停,它们会转而执行一些辅助标记(Mark Assist)以减慢继续分配的目的,协助 GC 标记。

如何观察GC

1、使用GODEBUG=gctrace=1$ GODEBUG=gctrace=1 go run main.go
测试代码片段

package main
import "sync"
func main() {
wg := sync.WaitGroup{} wg.Add(10) for i := 0; i < 10; i++ { go func(wg *sync.WaitGroup) { var counter int for i := 0; i < 1e10; i++ { counter++ } wg.Done() }(&wg) }
wg.Wait()
}分析结果

gc 1 @0.054s 5%: 0.024+37+0.48 ms clock, 0.098+4.5/12/53+1.9 ms cpu, 4->4->3 MB, 5 MB goal, 4 P
字段意义gc1第一个GC周期0.054s
程序开始后0.054秒
5%
本次GC周期的CPU使用率
0.024
标记开始时,STW花费的时间(wall time)
37
标记过程中,在标记上花费的时间(wall time)0.48
标记结束时,STW花费的时间(wall time)0.098
标记开始时,STW花费的时间(CPU time)
4.5
标记过程中,标记花费的时间(CPU time)
12
标记过程中,标记花费的时间(CPU time)
53
标记过程中,GC idle时间(CPU time)
1.9
标记结束时,STW花费的时间(CPU time)
4
标记开始时,堆栈的实际值
4
标记结束时,堆栈的实际值
3
标记结束时,标记为生存对象的大小
5
标记结束时,堆栈的预测值
4
p的数量

GC是如何优化的

多GC的优化主要考虑以下几个方面1. 控制内存分配的速度,限制 Goroutine 的数量,提高分配器对 CPU 的利用率。
2、减少和多路复用内存,比如使用Sync.pool来多路复用频繁创建的临时对象,比如提前有足够的内存来减少多余的拷贝。
3、需要的时候,增大GOGC的值,降低GC的运行频率。

GC进化过程

  • V1.0 - 完全串行的标签和清除过程,您需要暂停整个程序;
  • V1.1 - 多核并行垃圾回收的标记和清除阶段;
  • V1.3 - Runtime 基于只包含指针类型的值的假设,增加了对栈内存的精确扫描支持,真正的精确垃圾回收;
  • V1.5——基于三色标记清理的并发垃圾收集器;
  • V1.6 - 实现去中心化垃圾收集协调器;
  • V1.7 - 通过并行堆栈收缩将垃圾收集修复为 2 ms;
  • V1.8 - 使用混合写屏障将垃圾收集修复为 0.5 毫秒;
  • V1.9 - 去掉重新筛选被挂起程序的堆栈的过程;
  • V1.10 - 更新了垃圾回收频率转换器(PACER)的实现,分离了软硬大小的目标;
  • V1.12 - 使用新的标签终止算法简化垃圾收集器的几个阶段;
  • V1.13 - 通过新的Scavenger解析瞬时内存,将内存返回给操作系统;
  • v1.14 - 或者只存活一个版本的 Scavenger,一个新的页面分配器,在现有的可扩展性的情况下优化内存进程的分配速率,并引入异步占用,解决密集循环导致的 STW 时间长问题;

8
总结总结了GO中GO的一些场景。Go 使用三色标记回收策略,结合混合写屏障保证并发回收安全,但在回收期间仍然需要 STW。

原文链接:https://www.programmerall.com/article/31392094099/

如有侵权,请联系删除!

------------------- END -------------------

声明:我要去上班所有作品(图文、音视频)均由用户自行上传分享,仅供网友学习交流,版权归原作者itwangzi所有,原文出处。若您的权利被侵害,请联系删除。

本文标题:(golang需要开发栈pdf)(golang适合初学者项目源代码)
本文链接:https://www.51qsb.cn/article/m8whg.html

(0)
打赏微信扫一扫微信扫一扫QQ扫一扫QQ扫一扫
上一篇2023-02-18
下一篇2023-02-18

你可能还想知道

发表回复

登录后才能评论