前言

垃圾回收(Garbage Collection,简称GC)是编程语言中提供的自动的内存管理机制,自动释放不需要的对象,让出存储器资源,无需程序员手动执行。

垃圾回收主要有两种机制:

  1. 引用计数(Reference Counting:给每一个对象增加一个计数器,每当该对象被引用/解除引用时,则计数器自增/自减1;当该计数器归零时,则对象会被回收。
  2. 追踪(Tracing):定期遍历内存空间,从若干根存储对象开始查找与之相关的存储对象,然后标记其余的没有关联的存储对象,最后回收这些没有关联的存储对象占用的内存空间。 引用计数看上去非常容易理解,好像也每什么问题。但是,它在运行时,要考虑计数器的并发控制以及给计数器开辟额外的内存空间。同时,针对对象循环引用没有办法解决。Go语言采用的是追踪的方式,在扫描期间,GC过程和其他用户goroutine可并发运行,但需要一定时间的STW(stop the world),STW的过程中,CPU不执行用户代码,全部用于垃圾回收,这个过程的影响很大,Golang进行了多次的迭代优化来解决这个问题。

标记-回收算法(Mark and Sweep)

这是Go在V1.3版本之前的算法,顾名思义,回收主要包含两个步骤:1、标记;2、回收。第一步,暂停程序业务逻辑, 从一些根节点逐步遍历存活单元,找出所有可达的对象,然后做上标记。第二步, 标记完了之后,然后开始清除未标记的对象。第三步, 停止暂停,让程序继续跑。然后循环重复这个过程,直到process程序生命周期结束。

算法缺点

  • 程序暂停,从直观上来看,就是会出现卡顿现象。如果对象多或者链路复杂的话,整个回收时间就会变得很长,也就意味着卡顿时间会很长,这无疑是不合理的。
  • 标记需要扫描整个heap,开销大。
  • 清除数据会产生heap碎片。

在三色回收算法中,会区分堆上的对象和栈上的对象,借此机会记录一下堆栈(heap and stack)的区别。第一步,堆栈分为两个方面:

  • 数据结构的堆和栈
  • 内存分配方式的堆和栈

数据结构的堆和栈比较好理解,栈是先入后出的数据结构,有数组实现的静态栈,也有链表实现的动态栈。而堆则是树形结构,比如常见的最大堆和最小堆。

像垃圾回收这种,指的就是内存分配中的堆和栈。内存分配主要有以下三种:

  1. 从静态存储区域分配:它是由编译器自动分配和释放的,即内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在,直到整个程序运行结束时才被释放,如全局变量与const变量。
  2. 在栈上分配:它同样也是由编译器自动分配和释放的,即在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元将被自动释放。需要注意的是,栈内存分配运算内置于处理器的指令集中,它的运行效率一般很高,但是分配的内存容量有限。比如:i := 0 或 s := “abcdef”.
  3. 从堆上分配:也被称为动态内存分配,它是由程序员手动完成申请和释放的。即程序在运行的时候由程序员使用内存分配函数来申请任意多少的内存。比如:arr := make([]int, 10).

由此可见,内存分配的堆栈与数据结构中所阐述的堆栈有着本质的区别,这一点千万不要混淆。