GC

Table of contents

  1. 1. GC是什么?
  2. 2. 为什么需要GC?
  3. 3. 常见的GC算法
    1. 3.1. 引用计数法
    2. 3.2. 标记清除法
    3. 3.3. 复制法
    4. 3.4. 标记整理法
  4. 4. Go语言的GC算法
    1. 4.1. 为什么不需要内存整理(为什么不采用标记整理算法或复制算法 )?
    2. 4.2. 为什么不采用分代法?
  5. 5. Go的GC
    1. 5.1. 标记清除法的瓶颈

GC是什么?

GC(Garbage Collection)即垃圾回收,实质上就是回收垃圾内存。由于栈上的内存是有编译器分配和释放的,所以GC都是对堆上不再使用的内存进行回收,让这些内存能够再次被利用

为什么需要GC?

在真实业务场景中,对象个数非常多,引用关系错综复杂,如果全靠程序员手动释放内存很难避免指针悬挂错误。所以一门拥有自动识别并回收垃圾内存的语言(如Java、Python、Go)在开发效率上是远远高于没有GC机制的语言的

常见的GC算法

引用计数法

引用计数法会为每一个对象分配一个空间(“计数器”),这个空间专门用来存储对象被引用的次数。如果有其他其他对象引用A对象,那么A对象的计数器就会+1;反之,引用A的对象删除对A的引用后,A的计数器便会-1,当计数器减至0时,该对象的内存空间就会被回收
image.png

  • 优点:简单直接,回收速度快,不会出现内存耗尽或达到阈值才进行回收的现象
  • 缺点:
    • 需要额外空间维护对象的“引用”
    • 无法解决循环引用问题

标记清除法

标记清除法可以分为两步:

  • 标记对象:从根对象开始,遍历所有的对象及其子对象,并标记它们的可达状态

  • 清除对象:再一次遍历堆中中所有的对象,将没有被标记的对象删除
    image.png

  • 优点:简单易实现,不需要额外的存储空间,适用于对象个数少的场景

  • 缺点:会造成内存碎片,导致后续创建大对象时,即使有足够的内存空间也可能无法成功创建,降低了内存的使用率

复制法

复制法将内存分为大小相等的两块区域,每一时刻只能使用其中的一块内存。当一块区域的内存被用完时,就需要把该区域还在使用的对象移动到另一块区域,同时将已使用的内存全部清除
image.png

  • 优点:不会产生内存碎片,每次都是清除整块内存
  • 缺点:内存被一分为二,利用率低,如果对象个数较多,复制对象的过程耗时长,效率低下

标记整理法

标记整理法和标记清除法类似,同样是需要两阶段完成GC操作

  • 标记阶段:从GC的根节点遍历所有的对象,把还在使用的对象标记
  • 整理阶段:所有存活的对象被移动到空闲内存的一端,按照地址顺序排列,并更新对象引用指针。最后将末端地址之外的内存空间清除
    image.png

Go语言的GC算法

Go语言的GC采用的是并发三色标记法 + 屏障技术实现的。三色标记法本质就是标记清理算法,并没有对象分代、内存整理的行为,三色标记只是对其标记阶段的一种简单描述。

为什么不需要内存整理(为什么不采用标记整理算法或复制算法 )?

Go语言的内存分配算法是基于TCMalloc,虽然不能像标记整理法/复制法消除内存碎片,但也能有效的降低内存碎片率。此外,Thread Cache机制使得Go在大部分分配场景下可以避免使用锁,在高并发环境下具有极佳的性能优势

为什么不采用分代法?

分代法的最大优势就是区分长生命周期和短生命周期的对象,从而快速回收段生命周期对象。但Go语言在编译期会做逃逸分析,可以将短生命周期的struct分配到栈上,考虑到一方面栈上的内存回收是非常快的,另一方面在Go中短生命周期对象个数并不多,所以分代法在Go中优势并不明显。相反地,分代法需要额外的屏障来维护老年代对象对新年代对象的引用关系,增加了GC负担

Go的GC

标记清除法的瓶颈

标记清除法在GC之前需要STW,即暂停这个应用程序,否则会导致错误地删除被引用的对象。但STW会给引用性能带来极大的损耗

是什么,有什么优势,实际情况,特殊性,不适用