前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >golang 系列:啥是垃圾回收?

golang 系列:啥是垃圾回收?

原创
作者头像
lincoln
修改2021-08-02 10:19:54
3630
修改2021-08-02 10:19:54
举报
文章被收录于专栏:后端后端

摘要

golang 的三色标记法虽然没有 java 的内存回收机制成熟,但它细分了回收过程,通过写屏障技术,能和用户程序并发进行,这也一定程度的提高了内存回收速度。

一、为什么要有垃圾回收

我们都知道,当程序启动的时候,操作系统是会分配出栈区和堆区的,作为动态内存分配使用。

在栈区里分配的内存是可以自动管理的,一旦某个变量的作用域结束,就可以被自动回收了。

但是堆区就不是这样的了,堆区是属于程序员自己管理的区域,即使在某个作用域结束了,后续也能使用到该变量。

为此,程序员需要时刻关注内存的管理,否则将出现很多问题。例如内存一直在增长没有释放,则会出现内存溢出;内存释放后还继续访问,则会出现非法访问等。

因此,对内存的管理事关重要。然而,人为的管理内存始终存在隐患,谁也不能保证自己写的代码没有一点问题。

所以垃圾回收机制出现了,它是编程语言的设计者在程序运行时通过一定的策略,让闲置的内存被自动回收。

这也能让开发者更加专注于业务逻辑,减少额外的负担。

二、垃圾回收有哪些常用策略

1) 引用计数法

此算法为对象维护了一个计数值,当对象被引用时,计数值 +1,当对象的引用被释放时计数值 -1,直到计数值为 0 ,表示没有其他对象在使用它了,此时就可以进行回收动作了。

引用计数法算法简单,易于实现。但频繁的更新引用计数,也带来了一定的开销。而且对于循环引用的情况,计数值是归不了 0 的,此时就做不了回收了。

2) 标记-清除法

标记清除法是对对象定时的进行标记,分为正在被使用的和没有被使用这两类。

当标记完后就可以对没有被使用的这一类对象进行内存回收了。

标记清除法有个 Root 根对象,遍历搜索都是从 Root 根对象开始标记的。由于不存在对象跟 Root 循环引用的情况,所以总是能搜索到所有能达到的对象,依次标记。

如果循环对象没有被标记到,就表示没有被引用,就可以回收了,循环引用问题就解决了。

由于程序是动态在运行的,随时有可能会改变对象的引用指向。因此,在进行标记动作的时候需要 Stop the world(STW),也就是停止其他任务的执行,使得标记过程没有被打乱。

这也就意味着程序会短暂的停滞,对于响应要求高的程序而言,无疑是不能接受的。

三、golang 的垃圾回收

golang 采用了叫三色标记法的回收机制,它是第二种算法的变种,通过将标记清除过程细分了多个阶段,并采用了写屏障去感知引用的修改,使得垃圾回收动作能和用户程序并发的进行,大大缩短了 Stop The World 的同时,也保证了对象不被误清除。

三色标记法将对象分成了三种:

  • 白色对象:未被使用的对象;
  • 灰色对象:当前对象有引用对象,但是还没有对引用对象继续扫描过;
  • 黑色对象,对上面提到的灰色对象的引用对象已经全部扫描过了,下次不用再扫描它的引用对象了。

当垃圾回收开始时,Go 会把根对象标记为灰色,其他对象标记为白色,然后从根对象遍历搜索,按照上面的定义去不断的对灰色对象进行扫描标记。

(这里的根对象可以理解为指向堆内存区块的指针)

当没有灰色对象时,表示标记完成,然后就可以开始清除白色对象了。

三色标记法在正式标记前会进行 Stop The World,以便启动写屏障。当启动好后,就会停止 Stop The World。然后开始标记对象,在这标记过程中,是可以和用户程序一起并发执行的。

当所有的对象都标记完,也就是没有灰色对象可遍历搜索时,会再一次的 STW,做第二次的扫描。

利用之前启动的写屏障,将标记期间有过引用修改的对象重新标记为灰色,保证对象不会被误清。

当第二次扫描结束时,就可以开始真正的清除动作了,而且也是可以跟用户程序一起并发执行的。

后面用户程序即使再 new 了对象,分配了内存,也不会进行标记动作了,相当于这些新的对象是下次 GC 要处理的了。

而对于原先被标记为白色的对象,也就是再也没有被使用的对象,程序是引用不到的了。此时就可以大胆并发清除,不需要再次 Stop the world 了。

触发时机

Go 允许手动触发垃圾回收,但一般开发者比较少介入内存的管理,更多是让运行时 runtime 根据下面 2 种情况来进行垃圾回收:

  • 内存分配到一定大小时触发
  • 一定时间内没有触发过垃圾回收,则会开始进行 GC,一般这个时间是 2 分钟

结尾

虽然有 GC 帮我们做内存的管理,但资源不是无限的,一旦内存上涨,那我们就得学会查找问题了。

当内存一直没有释放时,我们可以使用 pprof 这些性能分析工具,帮助我们去做内存分析。

另外,在写代码时我们也可以从下面几个点进行内存的优化,让我们的程序更加健壮。

  • string 和 []byte 可以通过 unsafe 或 reflect 包进行强制转换,以减少内存拷贝。
  • 如果频繁的临时对象需要创建,则可以使用 sync.Pool 来重用对象。减少垃圾回收。
  • 如果能提前知道 slice 的大小,尽量预分配好它的容量,避免不断的 append 中,不断的扩容。

感兴趣的朋友可以搜一搜公众号「 阅新技术 」,关注更多的推送文章。

可以的话,就顺便点个赞、留个言、分享下,感谢各位支持!

阅新技术,阅读更多的新知识。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 摘要
  • 一、为什么要有垃圾回收
  • 二、垃圾回收有哪些常用策略
    • 1) 引用计数法
      • 2) 标记-清除法
      • 三、golang 的垃圾回收
      • 结尾
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档