Go内存原理-GC原理

简介: 本文介绍了Go语言中垃圾回收(GC)机制的发展与实现原理,涵盖从标记-清除算法到三色标记法,再到三色标记加混合写屏障的演进过程,重点解析各版本GC的核心思想、优缺点及性能优化方向。

前几章我们学习了Golang内存管理的基本原理(还不清楚内存管理的童鞋请移步看内存管理系列)。现在我们来看GC的基本原理是什么?

一、GC

GC(Garbage Collection)提供自动内存管理机制,自动释放回收不使用的内存对象,防止内存泄漏

GC的算法随着go语言版本的更新而不断变化

goV1.3之前标记-清除(mark and sweep)算法

goV1.5三色标记法

goV1.8三色标记法+混合写屏障法

我们将对以上算法进行原理剖析

触发GC的条件:

1)主动触发,用户代码中调用 runtime.GC 会主动触发 GC 2)默认每 2min 未产生 GC 时,golang 的守护协程 sysmon 会强制触发 GC 3)当 go 程序分配的内存增长超过阈值时,会触发 GC

二、标记-清除(mark-and-sweep)(v1.3)

1、介绍

从名字看就会有2个步骤,标记和清除

1)  暂停程序运行(STW) 对可达的对象进行标记 2)  清除未标记的对象 3)停止暂停,程序继续

引入STW(stop the world)是为了防止在标记过程中又发生了对象的引用,导致清除了正在使用的对象

2、优缺点

1)优点:简单 2)缺点:STW暂停程序,程序出现卡顿;标记需要扫描整个heap; 清除数据会产生碎片

三、三色标记(v1.5)

1、介绍

在标记-清除方法上增加了中间状态:黑色-灰色-白色 以减少STW的时间

黑色:已标记的对象,表示对象是跟对象的可达的

灰色:被黑色引用的对象,灰色为标记过程的中间状态,当灰色对象全部标记为黑色表示标记阶段全部完成

白色:未标记对象,在开始扫描时全部对象均为白色,若扫描完成后该节点还是白色,说明该对象不可达,在清除阶段会被清除

最后,只有2中颜色,白色被清除,黑色被保留

过程

1)开始时所有对象为白色 2)将所有根对象标记为灰色,放入队列 3)遍历灰色对象,将其标记为黑色,并将他们引用的对象标记为灰色,放入队列 4)重复步骤 3 持续遍历灰色对象,直至队列为空 5)此时只剩下黑色对象和白色对象,白色对象即为下一步需要清除的对象

根对象指的是运行当前时刻的栈以及全局数据区域

在三色标记中,减少了STW的时间。我们知道在三色标记中,增加了中间状态-灰色状态,这可以允许标记与用户代码并行执行,这里是怎么做到的呢,是因为三色标记加入了写屏障机制

写屏障机制:黑色对象的内存槽有两种位置, 栈和堆. 栈空间的特点是容量小,但是要求相应速度快,因为函数调用弹出频繁使用, 所以“写屏障”机制,在栈空间的对象操作中不使用. 而仅仅使用在堆空间对象的操作中。写屏障简单来说也就是对于已标记的黑色对象(堆)由于并发特性添加了新对象(白色),此时会触发写屏障,也就是会将添加的新对象标记为灰色,继续进行标记,最后标记完成后,刚刚标记的灰色也会变为黑色。这是对于堆而言。

若对于栈中已标记的黑色对象由于并发特性添加了新对象(白色)。在所有的标记完成之后,可能对于栈上还会存在白色对象被引用的情况,这个时候需要STW重新扫描栈,标记栈上引用的白色对象为灰色,继续扫描标记为黑色。这次STW大约的时间在10~100ms间

2、优缺点
  1. 优点:减少STW的时间
  2. 缺点:结束时需要STW来重新扫描栈,标记栈上引用的白色对象的存活

四、三色标记+混合写屏障法(v1.8)

1、介绍

避免了对栈重新扫描的过程,极大的减少了STW的时间。当然屏障技术的使用还是在堆上,要保证栈上的运行效率

1)GC开始将栈上的对象全部扫描并标记为黑色(之后不再进行第二次重复扫描,无需STW) 2)GC期间,任何在栈上创建的新对象,均为黑色 3)堆中被删除的对象标记为灰色(这里被删除是指的是删除引用关系) 4)堆中被添加的对象标记为灰色(这里被添加是指的是添加引用关系)

可见混合屏障只需要在开始时并发扫描各个goroutine的栈,使其变黑并一直保持,这个过程不需要STW,而标记结束后,因为栈在扫描后始终是黑色的,也无需再进行re-scan操作了,减少了STW的时间

2、优缺点

几乎不需要STW的时间,效率更高

引用


转载来源:https://juejin.cn/post/6930906179048570887

相关文章
|
缓存 Go API
Go 实现一个支持多种过期、淘汰机制的本地缓存的核心原理
本文旨在探讨实现一个支持多种 过期、淘汰 机制的 go 本地缓存的核心原理,我将重点讲解如何支持多样化的过期和淘汰策略。
314 0
|
7月前
|
人工智能 安全 Java
Go与Java泛型原理简介
本文介绍了Go与Java泛型的实现原理。Go通过单态化为不同类型生成函数副本,提升运行效率;而Java则采用类型擦除,将泛型转为Object类型处理,保持兼容性但牺牲部分类型安全。两种机制各有优劣,适用于不同场景。
252 24
|
7月前
|
存储 人工智能 安全
深入理解 go sync.Map - 基本原理
本文介绍了 Go 语言中 `map` 在并发使用时的常见问题及其解决方案,重点对比了 `sync.Mutex`、`sync.RWMutex` 和 `sync.Map` 的性能差异及适用场景。文章指出,普通 `map` 不支持并发读写,容易引发错误;而 `sync.Map` 通过原子操作和优化设计,在某些场景下能显著提升性能。同时详细讲解了 `sync.Map` 的基本用法及其适合的应用环境,如读多写少或不同 goroutine 操作不同键的场景。
292 1
|
9月前
|
安全 Go 开发者
Go语言之切片的原理与用法 - 《Go语言实战指南》
切片(slice)是Go语言中用于处理变长数据集合的核心结构,基于数组的轻量级抽象,具有灵活高效的特点。切片本质是一个三元组:指向底层数组的指针、长度(len)和容量(cap)。本文详细介绍了切片的声明与初始化方式、基本操作(如访问、修改、遍历)、长度与容量的区别、自动扩容机制、共享与副本处理、引用类型特性以及常见陷阱。通过理解切片的底层原理,开发者可以更高效地使用这一数据结构,优化代码性能。
319 13
|
9月前
|
人工智能 Go
[go]Slice 切片原理
本文详细介绍了Go语言中的切片(slice)数据结构,包括其定义、创建方式、扩容机制及常见操作。切片是一种动态数组,依托底层数组实现,具有灵活的扩容和传递特性。文章解析了切片的内部结构(包含指向底层数组的指针、长度和容量),并探讨了通过`make`创建切片、基于数组生成切片以及切片扩容的规则。此外,还分析了`append`函数的工作原理及其可能引发的扩容问题,以及切片拷贝时需要注意的细节。最后,通过典型面试题深入讲解了切片在函数间传递时的行为特点,帮助读者更好地理解和使用Go语言中的切片。
277 0
|
缓存 Java 编译器
Go 中的内存布局和分配原理
Go 中的内存布局和分配原理
|
Unix Go 开发者
探索Go语言并发模型:原理与实践
本文深入探讨了Go语言的并发模型,包括其设计原理、Goroutine和Channel的基本使用,以及如何在实际项目中高效地应用这些概念来提高程序的性能和可维护性。
|
存储 人工智能 JSON
深入理解 go reflect - 反射基本原理
深入理解 go reflect - 反射基本原理
231 0
|
缓存 安全 测试技术
深入理解 go sync.Map - 基本原理
深入理解 go sync.Map - 基本原理
228 0
|
存储 Java Go
Go 语言切片如何扩容?(全面解析原理和过程)
Go 语言切片如何扩容?(全面解析原理和过程)
911 2

热门文章

最新文章