这是我参与11月更文挑战的第7天,活动详情查看:2021最后一次更文挑战
GC在什么时候触发?如何调整触发频率?
主动触发
通过runtime.GC来触发GC,阻塞式地等待当前GC运行完毕。
被动触发
- 当超过两分钟没有产生任何GC时,强制触发GC。
- 使用步调(Pacing)算法,核心思想是控制内存增长的比例。
如何主动触发GC?
通过runtime.GC来触发GC
GC分为几个阶段?分别做什么?
以STW为界限,可以将GC划分为5个阶段:
| 阶段 | 说明 | 赋值器状态 |
|---|---|---|
| SweepTermination | 清扫终止阶段,为下一个阶段的并发标记做准备工作,启动写屏障 | STW |
| Mark | 扫描标记阶段,与赋值器并发执行,写屏障开启 | 并发 |
| MarkTermination | 标记终止阶段,保证一个周期内标记任务完成,停止写屏障 | STW |
| GCoff | 内存清扫阶段,将需要回收的内存归还到堆中,写屏障关闭 | 并发 |
| GCoff | 内存归还阶段,将过多的内存归还给操作系统,写屏障关闭 | 并发 |
GC采用的三色标记法原理是什么?在哪个阶段需要STW?
理解三色标记法的关键是理解对象的三色抽象以及波面(wavefront)推进这两个概念。
三色抽象
从垃圾回收器的视角来看,三色抽象规定了三种不同类型的对象,并用不同的颜色相称:
- 白色对象(可能死亡):未被回收器访问到的对象。在回收开始阶段,所有对象均为白色,当回收结束后,白色对象均不可达。
- 灰色对象(波面):已被回收器访问到的对象,但回收器需要对其中的一个或多个指针进行扫描,因为他们可能还指向白色对象。
- 黑色对象(确定存活):已被回收器访问到的对象,其中所有字段都已被扫描,黑色对象中任何一个指针都不可能直接指向白色对象。
波面
当垃圾回收开始时,只有白色对象。随着标记过程开始进行时,灰色对象开始出现(着色),这时候波面便开始扩大。当一个对象的所有子节点均完成扫描时,会被着色为黑色。当整个堆遍历完成时,只剩下黑色和白色对象,这时的黑色对象为可达对象,即存活;而白色对象为不可达对象,即死亡。这个过程可以视为以灰色对象为波面,将黑色对象和白色对象分离,使波面不断向前推进,直到所有可达的灰色对象都变为黑色对象为止的过程。
常见有哪些手段优化GC STW时间?
- 控制内存分配的速度,限制 goroutine 的数量,从而提高赋值器对 CPU 的利用率。
- 减少并复用内存,例如使用 sync.Pool 来复用需要频繁创建临时对象,例如提前分配足够的内存来降低多余的拷贝。
- 需要时,增大 GOGC 的值,降低 GC 的运行频率。
Golang内存分配算法是什么样的?
编程语言的内存分配器一般包含两种分配方法。
线性分配器
在内部维护一个指向内存特定位置的指针,当用户程序向分配器申请内存,分配器只需检查剩余的空闲内存、返回分配的内存区域并修改指针在内存中的位置
优点:较快的执行速度,较低的实现复杂度
缺点:无法在内存被释放时重用内存
空闲链表分配器
在内部维护一个类似链表的数据结构,当用户程序申请内存时,空闲链表分配器会依次遍历空闲的内存块,找到足够大的内存,然后申请新的资源并修改链表
优点:可以重新利用回收的资源
缺点:复杂度略高
该分配器有四种分配策略
- 首次适应(First-Fit)— 从链表头开始遍历,选择第一个大小大于申请内存的内存块;
- 循环首次适应(Next-Fit)— 从上次遍历的结束位置开始遍历,选择第一个大小大于申请内存的内存块;
- 最优适应(Best-Fit)— 从链表头遍历整个链表,选择最合适的内存块;
- 隔离适应(Segregated-Fit)— 将内存分割成多个链表,每个链表中的内存块大小相同,申请内存时先找到满足条件的链表,再从链表中选择合适的内存块;
Go使用的是第四种隔离适应策略,隔离适应的分配策略减少了需要遍历的内存块数量,提高了内存分配的效率。
核心理念是使用多级缓存将对象根据大小分类,并按照类别实施不同的分配策略。