Go语言垃圾回收GC(完整)

垃圾回收的概念

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(标记清除法)

1.3版本中,go runtime分离了mark和sweep操作,和以前⼀样,也是先暂停所有任务执⾏并启动
mark,mark完成后⻢上就重新启动被暂停的任务了,⽽是让sweep任务和普通协程任务⼀样并⾏的和其他任务⼀起执⾏。

v1.5:三⾊标记

三色抽象和垃圾回收

  • 白色对象:未被标记的对象,这些对象可能会在垃圾回收的过程中被回收。
  • 灰色对象:已被标记为活跃但其引用对象尚未完全扫描的对象。
  • 黑色对象:活跃且其所有引用对象都已经标记完毕,不会被回收。
go1.5正在实现的垃圾回收器“⾮分代的、⾮移动的、并发的、三⾊的标记清除垃圾收集器”。这种⽅法的mark操作可以渐进执⾏的⽽不需每次都扫描整个内存空间,可以减少stop the world的时间。

1.8:混合写屏障

由于标记操作和⽤⼾逻辑是并发执⾏的,⽤⼾逻辑会时常⽣成对象或者改变对象的引⽤。例如把⼀个对象标记为⽩⾊准备回收时,⽤⼾逻辑突然引⽤了它,或者⼜创建了新的对象。由于对象初始时都看为⽩⾊,会被 GC 回收掉。
GC 对扫描过后的对象使⽤操作系统写屏障功能来监控这段内存。如果这段内存发⽣引⽤改变,写屏障会给垃圾回收期发送⼀个信号,垃圾回收器捕获到信号后就知道这个对象发⽣改变,然后重新扫描这个对象,看看它的引⽤或者被引⽤是否改变。利⽤状态的重置实现当对象状态发⽣改变的时候,依然可以再次其引⽤的对象。

用下面这个例子解释并发带来的问题,当从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 // 子节点入队
}

垃圾回收三⾊标记法实现原理

  1. 所有对象初始都被标记为⽩⾊,表⽰这些对象尚未被扫描过。
  2.  从根对象开始(如全局变量、栈中的变量等),将其引⽤的对象标记为灰⾊,表⽰这些对象已经被 扫描过,但其引⽤的对象还未扫描。
  3. 继续对灰⾊对象进⾏扫描,将其引⽤的对象标记为灰⾊,并将当前灰⾊对象标记为⿊⾊,表⽰这些 对象已经被扫描过,其引⽤的对象也已被扫描过。
  4. 遍历所有⽩⾊对象,将其标记为死亡对象,进⾏垃圾回收。

垃圾回收的实现

Go采用“并发标记清除”来管理和回收不再使用的内存。

流程:

  1. 标记阶段:垃圾回收器从根对象(如全局变量、栈上的指针等)开始,通过遍历对象图的方式标记所有可达的对象。这个过程是并发进行的,与程序的执行同时进行,不会阻塞程序的运行。
  2. 并发标记阶段:在标记阶段的同时,Go语言的垃圾回收器还会与程序的执行并发的标记新创建的对象,这个过程通过与程序的执行并行运行,以减少程序性能的影响。
  3. 清除阶段:在标记阶段完成后,垃圾回收器会对堆中未标记对象进行清除。这个过程会暂停程序的执行,因为它需要遍历整个堆并回收未标记的对象。清除后的内存空间会被重新分配给新的对象使用。
  4. 并发清除阶段:在清除阶段完成之后,go语言的垃圾回收器会继续与程序的执行同时进行,以减少对程序性能的影响。

插⼊,删除,混合写屏障

屏障
Go 语⾔的屏障(Barrier)是⼀种同步机制,⽤于协调多个 Goroutine 的执⾏。在 Go 语⾔中,屏障通常⽤于等待⼀组 Goroutine 完成⼀定的任务,然后再继续执⾏下⼀步操作。
作⽤
Go 语⾔的屏障机制可以帮助开发者协调多个 Goroutine 的执⾏,避免出现竞争条件和死锁等问题,提⾼程序的可靠性和性能。
插⼊屏障
插⼊屏障(Barrier Insertion)是⼀种⽤于控制 Goroutine 之间交互的同步机制。在 Go 语言的垃圾回收器中,写屏障通过在每次写操作时,插入额外的代码来工作,具体来说,每次 Go 程序写操作(比如赋值语句)进行时,都会调用一个写屏障函数。对于标记-清除算法,写屏障会在新引用插入对象时,将该引用的对象标记为灰色。这种做法确保黑色对象不会直接指向白色对象,并且所有新的引用都会被回收器追踪。
删除屏障
删除屏障通(Barrier Elimination)常⽤于⼀些简单的同步操作,例如读写锁和互斥锁
等,当这些锁被频繁地获取和释放时,会产⽣较⼤的开销。在这种情况下,删除屏障可以通过使⽤
⼀些优化技术,例如锁消除和锁粗化,来避免不必要的同步操作,从⽽提⾼程序的性能。
混合写屏障
混合写屏障(Mixed-Mode Barrier)是 Go 语⾔中常⽤的同步机制之⼀,可以根据具体的需求和场景选择不同的屏障实现,从⽽更加灵活地协调多个 Goroutine 之间的交互,确保程序的正确性和可靠性。

GO语⾔什么时间会触发垃圾回收,如何调优?

Go 语言的 GC 触发机制主要是基于两种策略

  1.  内存分配量:当已分配的内存量达到了某个阈值时,GC 会被触发。这个阈值是基于已分配内存量和内存增长速率动态调整的。 
  2. 时间间隔:如果内存分配量没有达到触发 GC 的阈值,经过一段时间(默认2分钟)后,GC 会被触发以确保垃圾及时回收。

Go语⾔的垃圾回收器是⼀种⾃动内存管理机制,⽤于回收不再使⽤的内存。有以下三种情况

  1. 定时触发:默认是垃圾回收器会在每个堆分配周期后触发,堆分配周期由 GOGC 环境变量指 定,默认值为100。
  2. 内存分配触发:当程序进⾏内存分配时,如果当前可⽤内存不⾜,垃圾回收器会被触发,以回收不再使⽤的内存,并将其加⼊内存池中供后续使⽤。
  3. ⼿动触发:程序可以通过调⽤ runtime.GC() 函数⼿动触发垃圾回收器,以回收不再使⽤的内 存。

调优

  •  调整GOGC环境变量的值:垃圾回收器触发时间默认为100,如果内存使⽤量⼩,可以将其调⼤⼀些设置成200、300.
  •  减少内存分配:可以通过使⽤对象池、复⽤对象等技术来减少内存分配。
  • 避免⼤对象:⼤对象会占⽤⼤内存。
  •  使⽤指针:值类型相⽐于指针类型,值类型会被复制,增加内存。
  • 调整堆的⼤⼩
  • 分析GC⽇志

  • 25
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值