本文已参与「新人创作礼」活动,一起开启掘金创作之路
引言
Go 为什么要有GC ?这是本人遇到的百度面试题,以下是我的回答:
垃圾回收,是一种自动内存管理的机制,它能够主动释放不再需要的内存资源。
很显然,面试官对我的回答不满意,他说这是GC的作用,你没有考虑为什么需要GC?
所以是为什么呢?欢迎评论!!!
GC有什么作用?
GC,全称 Garbage Collection,即垃圾回收,是一种自动内存管理的机制。
当程序向操作系统申请的内存不再需要时,垃圾回收主动将其回收,并供其他代码进行内存申请的时候复用,或者将其归还给操作系统,这种针对内存级别资源的自动回收过程,即为垃圾回收。而负责垃圾回收的程序组件,即为垃圾回收器。
垃圾回收是一个完美的 “Simplicity is Complicated” 的例子。一方面,程序员受益于 GC,无需操心、也不再需要对内存进行手动的申请和释放操作,GC 在程序运行时自动释放残留的内存。另一方面,GC 对程序员几乎不可见,仅在程序需要进行特殊优化时,通过提供可调控的 API,对 GC 的运行时机、运行开销进行把控的时候才得以现身。
通常,垃圾回收器的执行过程被划分为两个半独立的组件:
- 赋值器(Mutator):这一名称本质上是指 用户态的代码。因为对垃圾回收器而言,用户态的代码仅仅只是在修改对象之间的引用关系,也就是在对象图(对象之间引用关系的一个有向图)上进行操作。
- 回收器(Collector):负责执行垃圾回收的代码。
什么是根对象?
根对象在垃圾回收的术语中又叫做根集合,它是垃圾回收器在标记过程时最先检查的对象,包括:
- 全局变量:程序在编译期就能确定的那些存在于程序整个生命周期的变量。
- 执行栈:每个 goroutine 都包含自己的执行栈,这些执行栈上包含栈上的变量及指向分配的堆内存区块的指针。
- 寄存器:寄存器的值可能表示一个指针,参与计算的这些指针可能指向某些赋值器分配的堆内存区块。
垃圾回收算法
业界常见的垃圾回收算法有以下三种:
-
引用计数: 对每个对象维护一个引用计数,当引用该对象的对象被销毁时,引用计数减1,当引用计数器为0时回收该对象。
优点: 对象可以很快被回收,不会出现内存耗尽或者达到某个阈值时才被回收;
缺点: 不能很好的处理循环引用,而且实时维护引用计数也有一定的代价;
代表语言: Python、PHP、Swift、Objective-C
-
标记—清除: 从根变量开始遍历所有的引用对象,引用的对象标记为“被引用”,没有被标记的对象被回收。
优点: 解决了引用计数的缺点;
缺点: 需要STW(Stop The World),即暂时停止程序运行,专心做垃圾回收,垃圾回收结束后再恢复;
代表语言: Go(其采用三色标记法)java js
-
分代收集: 按照对象生命周期的长短划分不同的代空间,生命周期长的放入老年代,而短的放入新生代,不同代有不同的回收算法和回收频率。
三色标记原理
原理:
首先把所有的对象都放到白色的集合中
- 从根节点开始遍历对象,遍历到的白色对象从白色集合中放到灰色集合中
- 遍历灰色集合中的对象,把灰色对象引用的白色集合的对象放入到灰色集 合中,同时把遍历过的灰色集合中的对象放到黑色的集合中
- 循环步骤 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 如何调优
通过 go tool pprof 和 go tool trace 等工具
- 控制内存分配的速度,限制 Goroutine 的数量,从而提高赋值器对 CPU 的利用率。
- 减少并复用内存,例如使用 sync.Pool 来复用需要频繁创建临时对象,例 如提前分配足够的内存来降低多余的拷贝。
- 需要时,增大 GOGC 的值,降低 GC 的运行频率。