Go 为什么要有GC ?

460 阅读5分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路

引言

Go 为什么要有GC ?这是本人遇到的百度面试题,以下是我的回答:

垃圾回收,是一种自动内存管理的机制,它能够主动释放不再需要的内存资源。

很显然,面试官对我的回答不满意,他说这是GC的作用,你没有考虑为什么需要GC?

所以是为什么呢?欢迎评论!!!

GC有什么作用?

GC,全称 Garbage Collection,即垃圾回收,是一种自动内存管理的机制。

当程序向操作系统申请的内存不再需要时,垃圾回收主动将其回收,并供其他代码进行内存申请的时候复用,或者将其归还给操作系统,这种针对内存级别资源的自动回收过程,即为垃圾回收。而负责垃圾回收的程序组件,即为垃圾回收器。

垃圾回收是一个完美的 “Simplicity is Complicated” 的例子。一方面,程序员受益于 GC,无需操心、也不再需要对内存进行手动的申请和释放操作,GC 在程序运行时自动释放残留的内存。另一方面,GC 对程序员几乎不可见,仅在程序需要进行特殊优化时,通过提供可调控的 API,对 GC 的运行时机、运行开销进行把控的时候才得以现身。

通常,垃圾回收器的执行过程被划分为两个半独立的组件:

  • 赋值器(Mutator):这一名称本质上是指 用户态的代码。因为对垃圾回收器而言,用户态的代码仅仅只是在修改对象之间的引用关系,也就是在对象图(对象之间引用关系的一个有向图)上进行操作。
  • 回收器(Collector):负责执行垃圾回收的代码。

什么是根对象?

根对象在垃圾回收的术语中又叫做根集合,它是垃圾回收器在标记过程时最先检查的对象,包括:

  1. 全局变量:程序在编译期就能确定的那些存在于程序整个生命周期的变量。
  2. 执行栈:每个 goroutine 都包含自己的执行栈,这些执行栈上包含栈上的变量及指向分配的堆内存区块的指针。
  3. 寄存器:寄存器的值可能表示一个指针,参与计算的这些指针可能指向某些赋值器分配的堆内存区块。

垃圾回收算法

业界常见的垃圾回收算法有以下三种:

  • 引用计数: 对每个对象维护一个引用计数,当引用该对象的对象被销毁时,引用计数减1,当引用计数器为0时回收该对象。

    优点: 对象可以很快被回收,不会出现内存耗尽或者达到某个阈值时才被回收;

    缺点: 不能很好的处理循环引用,而且实时维护引用计数也有一定的代价;

    代表语言: Python、PHP、Swift、Objective-C

  • 标记—清除: 从根变量开始遍历所有的引用对象,引用的对象标记为“被引用”,没有被标记的对象被回收。

    优点: 解决了引用计数的缺点;

    缺点: 需要STW(Stop The World),即暂时停止程序运行,专心做垃圾回收,垃圾回收结束后再恢复;

    代表语言: Go(其采用三色标记法)java js

  • 分代收集: 按照对象生命周期的长短划分不同的代空间,生命周期长的放入老年代,而短的放入新生代,不同代有不同的回收算法和回收频率。

三色标记原理

img

原理:

首先把所有的对象都放到白色的集合中

  • 从根节点开始遍历对象,遍历到的白色对象从白色集合中放到灰色集合中
  • 遍历灰色集合中的对象,把灰色对象引用的白色集合的对象放入到灰色集 合中,同时把遍历过的灰色集合中的对象放到黑色的集合中
  • 循环步骤 2,直到灰色集合中没有对象
  • 步骤 3结束后,白色集合中的对象就是不可达对象,也就是垃圾,进行回收

GC 触发时机

内存分配量达到阈值触发GC

  • 使用步调(Pacing)算法,其核心思想是控制内存增长的比例。

每次内存分配时都会检查当前内存分配量是否已达到阈值,如果达到阈值立即启动GC,

阈值 = 上次GC内存分配量 × 内存增长率

内存增长率由环境变量GOGC控制,默认为100,即每当内存扩大一倍时启动GC。

定期触发GC

  • 使用系统监控,默认情况下,当超过两分钟没有产生任何 GC 时,强制触发 GC。这个时间间隔由 runtime.forcegcperiod变量声明

主动触发

  • 程序代码中可以调用 runtime.GC()来触发GC,此调用阻塞式地等待当前 GC 运行完毕。主要用于GC的性能测试和统计。

Go 语言中 GC 的流程是什么?

(Go1.14以后)当前版本的 Go 以 STW 为界限,可以将 GC 划分为五个阶段:

阶段说明赋值器状态
SweepTermination清扫终止阶段,为下一个阶段的并发标记做准备工作,启动写屏障STW
Mark扫描标记阶段,与赋值器并发执行,写屏障开启并发
MarkTermination标记终止阶段,保证一个周期内标记任务完成,停止写屏障STW
GCoff内存清扫阶段,将需要回收的内存归还到堆中,写屏障关闭并发
GCoff内存归还阶段,将过多的内存归还给操作系统,写屏障关闭并发

具体而言,各个阶段的触发函数分别为:

gc-process

GC 如何调优

通过 go tool pprof 和 go tool trace 等工具

  • 控制内存分配的速度,限制 Goroutine 的数量,从而提高赋值器对 CPU 的利用率。
  • 减少并复用内存,例如使用 sync.Pool 来复用需要频繁创建临时对象,例 如提前分配足够的内存来降低多余的拷贝。
  • 需要时,增大 GOGC 的值,降低 GC 的运行频率。