Go语言的内存管理 | 青训营笔记

91 阅读4分钟

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) 可以同时执行

image.png

二、追踪垃圾回收

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 语言性能优化及自动内存管理

图文结合,白话 Go 的垃圾回收原理

GO语言的垃圾回收