Go语言的内存管理 | 青训营笔记
这是我参与「第五届青训营 」伴学笔记创作活动的第4天
一、Go语言自动内存管理
1.1 自动内存管理的基本概念
动态内存:
- 程序在运行时根据需求动态分配的内存:类似于C语言的
malloc()
自动内存管理(垃圾回收):由程序语言的运行时系统管理动态内存。
- 避免手动内存管理,专注于实现业务逻辑(勤奋的目的是为了懒)
- 保证内存使用的 正确性 和 安全性 :防止出现
double-free problem & use-after-free problem
1.2 三个任务
1.为新对象分配空间
2.找到存活对象
3.回收死亡对象的内存空间
1.3 相关概念
什么是GC ?
现代高级编程语言管理内存的方式分为两种:自动和手动 ,像C、C++ 等编程语言使用手动管理内存的方式,工程师编写代码过程中需要主动申请或者释放内存;而 PHP、Java 和 Go 等语言使用自动的内存管理系统,有内存分配器和垃圾收集器来代为分配和回收内存,其中垃圾收集器就是我们常说的GC。
GC算法的种类:
主流的垃圾回收算法有两大类,分别是追踪式垃圾回收算法和引用计数法( Reference counting )。
- Mutator:业务线程,分配新对象,修改对象指向关系
- Collector:GC线程,找到存活对象,回收死亡对象的内存空间
- Serial GC:只有一个collector
- Parallel GC:支持多个collectors同时回收的GC算法
- Concurrent GC:mutator(s) 和 collector(s) 可以同时执行
二、追踪垃圾回收
2.1 核心思想
追踪式算法的核心思想是判断一个对象是否可达,一旦这个对象不可达就可以在垃圾回收的控制循环里被 GC 回收了。那么我们怎么判断一个对象是否可达呢?很简单:
- 第一步找出所有的全局变量和当前函数栈里的变量,标记为可达。
- 第二步,从已经标记的数据开始,进一步标记它们可访问的变量,以此类推。
2.2 标记清除算法步骤
此算法主要有两个步骤:
- 暂停应用程序的执行, 从根对象出发标记出可达对象。
- 清除未标记的对象,恢复应用程序的执行。
2.3 标记清除的缺点
这个算法最大的问题是 GC 执行期间需要把整个程序完全暂停,不能异步地进行垃圾回收,对实时性要求高的系统来说,这种需要长时间挂起的标记清扫法是不可接受的。所以就需要一个算法来解决 GC 运行时程序长时间挂起的问题。
- STW, 会让程序变慢
- 标机过程需要将全部的堆栈进行一遍扫描来确定是不是可达对象
- 删除的过程中, 会产生heap碎片
三、引用计数
3.1 核心思想
每个单元维护一个域,保存其它单元指向它的引用数量(类似有向图的入度)。当引用数量为0时,将其回收。引用计数是 渐进式 的,能够将内存管理的开销分布到整个程序之中。C++ 的 share_ptr 使用的就是引用计算方法。
3.2 优点
渐进式。 内存管理与用户程序的执行交织在一起,将 GC 的代价分散到整个程序。不像标记-清扫算法需要 STW (Stop The World,GC 的时候挂起用户程序)。
算法易于实现。
内存单元能够很快被回收。 相比于其他垃圾回收算法,堆被耗尽或者达到某个阈值才会进行垃圾回收。
3.3 缺点
原始的引用计数不能处理循环引用。 大概这是被诟病最多的缺点了。不过针对这个问题,也除了很多解决方案,比如强引用等。
维护引用计数降低运行效率。 内存单元的更新删除等都需要维护相关的内存单元的引用计数,相比于一些追踪式的垃圾回收算法并不需要这些代价。
单元池 free list 实现的话不是 cache-friendly 的,这样会导致频繁的 cache miss,降低程序运行效率。
四、引用参考:
该文章部分内容来自于以下课程或网页:
字节内部课:Go 语言性能优化及自动内存管理