golang 重要知识:垃圾回收

news/2024/7/24 10:57:39

摘要

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 中,不断的扩容。

感兴趣的朋友可以搜一搜公众号「 阅新技术 」,关注更多的推送文章。
可以的话,就顺便点个赞、留个言、分享下,感谢各位支持!
阅新技术,的新知识。
阅新技术


http://www.niftyadmin.cn/n/1251133.html

相关文章

面试官问Vue性能优化,我该如何回答?

最近面试Vue性能优化问得比较多,也有很多通过面试能拿到高薪。一个前辈闲谈,问他怎么看待工作 2 年的前端开发,月薪就高达 30k、40k 的现状。他说,在众多编程技术中,前端算比较容易入门和提升的,可以通过页…

浪潮的加班标语炸了,这是顶风作案?

近日,网传“浪潮”在办公室挂上了鼓励大家加班的标语。标语 1 :有空就去“加班”吧,去完成那些我们还未完成的事!标语 2 :白天加白班,不瞌睡;晚上加晚班,睡不着。标语 3:…

golang 重要知识:channel 用法和底层原理

前言 channel 是 goroutine 与 goroutine 之间通信的重要桥梁,借助 channel,我们能很轻易的写出一个多协程通信程序。今天,我们就来看看这个 channel 的常用用法以及底层原理。 一、channel 的概念 channel 是一个通道,用于端到…

Chrome 版本即将突破100 ?这个问题不容忽视!

今天跟大家来聊一个由于浏览器版本的变更可能会带来的问题,大家可以提前为自己的网站测试一下看看会不会有问题。2022 年上半年,Chrome 马上就要达到三位数的主要版本号:100 了!记得 Chrome 在很久以前第一次达到版本号 10 时&…

如何进入、退出docker的container

1 启动docker服务 首先需要知道启动docker服务是: service docker start 或者: systemctl start docker 2 关闭docker服务 关闭docker服务是: service docker stop 或者: systemctl stop docker 3 启动docker某个image&am…

golang 重要知识:深入认识 map

摘要 map 通过 hasTable 实现了我们最常见的 key-value 存储,能快速的对数据集增删查改。同时 Go 里的 map 也有很多特殊的地方,比如它的无序性、并发不安全等。今天,就让我们对 map 进行深入研究,看看它是怎么设计的。 map 基本…

yum安装最新稳定版本nginx

首先进入官网 ,详细安装在http://nginx.org/en/linux_packages.html 1、首先添加一下nginx的官网yum源,命令如下: vim /etc/yum.repos.d/nginx.repo 进去加入以下内容,根据你自己的系统选择: [nginx] namenginx repo baseurlh…

面试官:Webpack 究竟打包出来的是什么?

前言Webpack 作为普遍使用的打包工具,在开发和发布的过程中产出的代码结构,你是否关心过?本文为你揭开它的神秘面纱。1、开发模式一般情况,开发的过程都会使用 devServer 并开启 hot 热更新。假如我们有一个页面入口文件 index.js…