高性能 Go 语言发行版优化与落地实践 | 青训营笔记

89 阅读2分钟

这是我参与「第三届青训营 -后端场」笔记创作活动的的4篇笔记。

image.png

自动内存管理

追踪垃圾回收

追踪垃圾回收其实就是对当前程序运行的内存做一个扫描,标记出当前程序没有任何生命周期内的指针指向的内存,因为Golang中不存在指针运算,所以这个内存一定不可能再被用户程序使用,于是就可以对该内存进行回收。

  • 对象被回收的条件:指针指向关系不可达的对象
  • 标记根对象:静态变量、全局变量、常量、线程栈等
  • 标记:从根对象出发,找到所有可达对象

image.png

  • 清理:所有不可达对象。根据对象的生命周期,使用不同的标记和清理策略
    • 将存活对象复制到另外的空间
    • 将死亡对象的内存标记为可分配 image.png image.png image.png

分代GC(Generational GC)

分代GC其实就是前面所说的根据对象生命周期不同来制定不同的回收策略,所依赖的最主要特性其实就是大多数动态分配的内存都很快不再使用了,往往都抗不过几轮GC,我们将其称为年轻代,年轻代的数量一般都并不大(因为死的快),所以直接采用Copying collection,回收速度就很快;另外生命周期很长的内存,如果每次都复制那开销就太大了,则采用Mark-sweep GC的方式回收。

引用计数

引用计数的方式与前面追踪式回收区别较大,其核心思路是对于每个动态分配的内存都维护一个引用数目,代表程序中有多少个变量正在使用这个内存(学过C++的同学应该很熟悉智能指针的概念,智能指针就是采用的引用计数的方式来实现的自动回收)。自然而然地,当引用数目为0时,就没有变量可以访问这个地址了,于是可以回收。

优点:

  • 内存管理的操作被平摊到程序的执行过程中
  • 内存管理不需要知道Runtime的实现(C++智能指针实现原理)

缺点:

  • 维护引用数目的额外开销较大:需要通过原子操作保证对引用计数的操作的原子性和可见性
  • 无法回收环形数据结构(Weak reference可解决此问题)
  • 内存开销:每个对象都需要引入额外的内存空间来存储数目
  • 回收大对象时仍可能发生暂停(牵一发而动全身),如下图

image.png