垃圾回收的概念
GC(垃圾回收)是 Go 语言中的一个重要机制,用于自动管理内存
在 Go 语言中,GC 会自动发现和回收那些不再被使用的内存空间,从而防止内存泄漏和有
效利用内存。
内存垃圾怎样产生
- 程序在内存上被分为堆区、栈区、全局数据区、代码段、数据区五个部分。
- 对于某些早期的编程语言栈上的内存由编译器管理回收,堆上的内存空间需要程序员负责申请与释放。
- Go中的栈上内存仍由编译器负责管理回收,而堆上的内存由编译器和垃圾收集器负责管理回收。
- 垃圾是指程序向堆栈申请的内存空间,随着程序的运行已经不再使用这些内存空间,这时如果不释放他们就会造成垃圾也就是内存泄漏。
GC 的工作原理
GC的核心思想是自动找到那些不再被引用的内存块,然后将其释放。Go 语言使用的是增强型标记-清除算法。简单来说,这个算法会先遍历所有活跃的变量并进行标记,然后清除未被标记的变量(不可达对象)。不过,Go的GC经过多次优化,能够在程序运行的同时有效地完成这些任务,从而减少对程序性能的影响。
GC 的优势
- 自动化内存管理:我们不需要手动释放内存,减少了内存错误的几率
- 提升开发效率:可以更专注于业务逻辑的开发,而不用担心内存管理,
- 安全性高:通过自动管理内存,避免了内存泄漏和悬空指针等问题。
GC 的影响与优化
尽管 GC 提高了内存管理的便利性,但是也会有一定的性能开销,可以优化
- 减少内存分配:尽量重用对象,避免频繁的内存分配和释放;
- 合并小对象:减少小对象的分配数量,通过合并提升内存使用效率:
- 调整 GC 触发阈值:根据程序的具体需求,调整 GC 的触发阈值,从而优化性能
手动内存管理
虽然 Go 语言中的 GC机制非常优秀,但在一些极端情况下,手动管理内存仍然是必要的。这包括使用 sync.Pool 等工具来管理对象池,减少 GC 触发的频率。
go的GC版本迭代
V1.3以前:STW
go runtime在一定条件下(内存超过阈值或定期如2min),暂停所有任务的执行,进行mark(标记)和sweep(清扫)操作,操作完成后启动所有任务的执行。在内存使用较多的场景下,go程序在进行垃圾回收时会发生非常明显的卡顿现象。
V1.3:mark STW & sweep(标记清除法)
v1.5:三⾊标记
三色抽象和垃圾回收
- 白色对象:未被标记的对象,这些对象可能会在垃圾回收的过程中被回收。
- 灰色对象:已被标记为活跃但其引用对象尚未完全扫描的对象。
- 黑色对象:活跃且其所有引用对象都已经标记完毕,不会被回收。
1.8:混合写屏障
用下面这个例子解释并发带来的问题,当从A这个GC root找到引用对象B时,B变灰A变黑。这时用户goroutine执行把A到B的引用改成了A到C的引用,同时B不再引用C。然后GC goroutine又执行,发现B没有引用对象,B变黑。而这时由于A已经变黑完成了扫描,C将当做白色不可达对象被清除。
解决办法:引入写屏障。当发现A已经标记为黑色了,若A又引用C,那么把C变灰入队。这个write_barrier是编译器在每一处内存写操作前生成一小段代码来做的。
// 写屏障伪代码
write_barrier(obj,field,newobj){
if(newobj.mark == FALSE){
newobj.mark = TRUE
push(newobj,$mark_stack)
}
*field = newobj
}
如何非递归的实现遍历mark可达节点,显然需要一个队列。
这个队列也帮助区分黑色对象和灰色对象,因为标记位只有一个。标记并且在队列中的是灰色对象,标记了但是不在队列中的黑色对象,末标记的是白色对象。
root node queue
while(queue is not nil) {
dequeue // 节点出队
process // 处理当前节点
child node queue // 子节点入队
}
垃圾回收三⾊标记法实现原理
- 所有对象初始都被标记为⽩⾊,表⽰这些对象尚未被扫描过。
- 从根对象开始(如全局变量、栈中的变量等),将其引⽤的对象标记为灰⾊,表⽰这些对象已经被 扫描过,但其引⽤的对象还未扫描。
- 继续对灰⾊对象进⾏扫描,将其引⽤的对象标记为灰⾊,并将当前灰⾊对象标记为⿊⾊,表⽰这些 对象已经被扫描过,其引⽤的对象也已被扫描过。
- 遍历所有⽩⾊对象,将其标记为死亡对象,进⾏垃圾回收。
垃圾回收的实现
Go采用“并发标记清除”来管理和回收不再使用的内存。
流程:
- 标记阶段:垃圾回收器从根对象(如全局变量、栈上的指针等)开始,通过遍历对象图的方式标记所有可达的对象。这个过程是并发进行的,与程序的执行同时进行,不会阻塞程序的运行。
- 并发标记阶段:在标记阶段的同时,Go语言的垃圾回收器还会与程序的执行并发的标记新创建的对象,这个过程通过与程序的执行并行运行,以减少程序性能的影响。
- 清除阶段:在标记阶段完成后,垃圾回收器会对堆中未标记对象进行清除。这个过程会暂停程序的执行,因为它需要遍历整个堆并回收未标记的对象。清除后的内存空间会被重新分配给新的对象使用。
- 并发清除阶段:在清除阶段完成之后,go语言的垃圾回收器会继续与程序的执行同时进行,以减少对程序性能的影响。
插⼊,删除,混合写屏障
屏障
作⽤
插⼊屏障
删除屏障
混合写屏障
GO语⾔什么时间会触发垃圾回收,如何调优?
Go 语言的 GC 触发机制主要是基于两种策略
- 内存分配量:当已分配的内存量达到了某个阈值时,GC 会被触发。这个阈值是基于已分配内存量和内存增长速率动态调整的。
- 时间间隔:如果内存分配量没有达到触发 GC 的阈值,经过一段时间(默认2分钟)后,GC 会被触发以确保垃圾及时回收。
Go语⾔的垃圾回收器是⼀种⾃动内存管理机制,⽤于回收不再使⽤的内存。有以下三种情况
- 定时触发:默认是垃圾回收器会在每个堆分配周期后触发,堆分配周期由 GOGC 环境变量指 定,默认值为100。
- 内存分配触发:当程序进⾏内存分配时,如果当前可⽤内存不⾜,垃圾回收器会被触发,以回收不再使⽤的内存,并将其加⼊内存池中供后续使⽤。
- ⼿动触发:程序可以通过调⽤ runtime.GC() 函数⼿动触发垃圾回收器,以回收不再使⽤的内 存。
调优
- 调整GOGC环境变量的值:垃圾回收器触发时间默认为100,如果内存使⽤量⼩,可以将其调⼤⼀些设置成200、300.
- 减少内存分配:可以通过使⽤对象池、复⽤对象等技术来减少内存分配。
- 避免⼤对象:⼤对象会占⽤⼤内存。
- 使⽤指针:值类型相⽐于指针类型,值类型会被复制,增加内存。
- 调整堆的⼤⼩
- 分析GC⽇志