这是我参与[第三届青训营-后端场]笔记创作活动的第1篇笔记
自动内存管理技术
- 追踪垃圾回收
回收对象:不可达的对象。 回收开始时会扫描gc roots,例如全局变量,栈的对象,静态变量,常量等。 从gc roots出发,沿着指针追踪扫描所有可达对象。 扫描结束后回收不可达对象。
-
go的GC机制
- go的GC机制是无分代,不整理(无拷贝移动),并发的三色标记清扫算法:
- 对象整理是为了避免内存碎片问题,go的内存分配采用tcmalloc,可以有效的减少内存碎片。
- go的编译器会通过逃逸分析把不会逃逸的大部分新生变量分配到栈上,这些变量会被栈直接回收,不需要通过GC回收,而长期生存的变量一般都分配到堆上。而分代假设主要是将GC目标放在新生变量上,这对go来说性能提升不是很大。
- 三色标记法
- 白色对象:未被回收器访问到的对象。回收开始前所有对象都是白色的,扫描结束后,白色对象为不可达。
- 灰色对象:已被回收器访问到的对象,但回收器还需要对其中一个或多个指针对象进行扫描。
- 黑色对象:已被回收器访问到的对象,且其中所有指针对象都被扫描或不存在指针对象。
- 如何保证垃圾回收的正确性
- 一个白色对象被黑色对象引用 (白色被挂在黑色下)
- 灰色对象与它之间的可达关系的白色对象遭到破坏 (灰色同时丢了该白色) 以上任两个条件同时满足就会破坏垃圾回收的正确行。
- 不变性
- 强三色不变性。黑色对象不对指向白色对象。(插入写屏障)
- 弱三色不变性。黑色对象可以指向白色对象,当且仅当存在从灰色对象出发可以找到该白色对象的路径。(删除写屏障)
- 赋值器的颜色
- 黑色赋值器:回收器扫描过,不会再对其进行扫描。
- 灰色赋值器:回收器没扫描或者扫描过但还需重新扫描。
-
在强三色不变性中,黑色赋值器只存在到黑色对象或灰色对象的指针,因为此时所有黑色对象指向白色对象是禁止的。
-
在弱三色不变性中,黑色赋值器允许存在到白色对象的指针,但这个白色对象应处于保护状态下。
-
写屏障(由于go协程操作都在栈上,所以栈使用写屏障hook效率太低,写屏障只使用堆内存)
- 插入写屏障(dijkstra write barrier):
- 删除写屏障(yuasa)
- 混合写屏障(hybrid)
继承了插入写屏障的优点,
-
GC流程
- Sweep Termination: 对未清扫的span进行清扫, 只有上一轮的GC的清扫工作完成才可以开始新一轮的GC(新GC会协助清理上一个GC的sweep)
- Mark: (allcoBits,gcmarkBits)
- Mark Prepare:初始化GC任务,包括开启写屏障(write barrier)和辅助GC(mutator assist),统计root对象的任务数量等,这个过程需要STW。
- GC Drains: 扫描所有root对象,包括全局指针和goroutine(G)栈上的指针(扫描对应G栈时需停止该G),将其加入标记队列(灰色队列),并循环处理灰色队列的对象,直到灰色队列为空。该过程后台并行执行。
- Mark Termination: 完成标记工作, 重新扫描部分根对象(要求STW)
- Sweep: 按标记结果清扫span(一个work后台线程,一个是即时清理:mcache向mcentral申请时再做清除工作。)
目前整个GC流程会进行两次STW(Stop The World), 第一次是Mark阶段的开始, 第二次是Mark Termination阶段.
- 第一次STW会准备根对象的扫描, 启动写屏障(Write Barrier)和辅助GC(mutator assist).
- 第二次STW会重新扫描部分根对象, 禁用写屏障(Write Barrier)和辅助GC(mutator assist).
需要注意的是, 不是所有根对象的扫描都需要STW, 例如扫描栈上的对象只需要停止拥有该栈的G.从go 1.9开始, 写屏障的实现使用了Hybrid Write Barrier, 大幅减少了第二次STW的时间。
- 优化技巧
- 尽量避免逃逸。
- 使用sync.Pool,重用内存。
- 引用计数
- 优点
- 内存管理的操作均摊到程序执行中。
- 内存管理不需要了解runtime的实现细节。
- 确定
- 维护引用计数,需要原子操作。
- 无法回收环形结构。weak reference。
- 额外的内存开销,存储引用计数。
- 大量内存需要回收时可能引起程序暂停。
- go的内存分配
采用的是类似TCMalloc的分配算法。
- Page:和tcmalloc的page一样为8KB。
- Span:代码中为mspan,由一个或多个page组成,内存管理的基本单位。
- mcache:类似tcmalloc的线程缓存,但是go中是一个P对应一个mcache。mcache保存了各种大小不同的span,并按span class划分,小对象<=32KB直接在mcache分配,起到了缓存作用,并且是无锁分配。
- mcentral:与tcmalloc的CentralCache一样,是全局缓存,需要加锁访问。同样是按span class分类,多个span串成链表。一个mcentral对应一个span class,有两个链表,分别是nonempty和empty。nonempty链表中的span都至少有一个空闲的对象空间。而empty链表中的span没有空闲的对象空间,已经被mcache取走但还未归还的span。
- mheap:和tcmalloc的pageheap类似,堆内存的抽象,把从os申请的page组成span并保存起来。mheap把span组织成二叉排序树而不是链表,分为free(空闲并非垃圾回收过的)和scav(被垃圾回收过的),并用heapArena进行管理。
编译优化
- 函数内联