这是我参与「第三届青训营 -后端场」笔记创作活动的的第六篇笔记
追踪垃圾回收
对象被回收的条件:指针指向关系不可达的对象
标记根对象
- 静态变量、全局变量、常量、线程栈等
标记过程:找到可达对象
- 求指针指向关系的传递闭包:从根对象出发,找到所有可达对象
三色标记法:
- 初始时所有对象均在白色集合
- 然后再遍历白色集合中的所有根对象,并把遍历到的对象加入灰色集合
- 遍历灰色集合,将灰色集合的引用从白色集合加入灰色集合,自身加入黑色集合。
- 重复第三步,直到灰色集合为空
引用计数
每个对象都有一个与之对应的引用数目,当且仅引用数大于0对象才能存活。
优点:
- 内存管理的操作被平分到程序执行的过程中
缺点:
- 维护开销大,通过原子操作保证引用计数的原子性和可见性。
- 无法回收循环引用的数据。
- 内存开销:每个对象都引入额外的内存空间存储引用数目
清理的方式:
- 将存活对象复制到另一块内存空间(copying GC)
- 将死亡对象的内存标记为
可分配
,后续可以继续分配给其它对象 (Mark-sweep GC) - 把存活对象移动到一起(Compact GC)
分代GC
年轻代
- 常用的对象分配
- 由于存活对象很少,可以采用copyiny collection
- GC吞吐率很高(GC时间/程序执行总时间)
老年代
- 对象趋于一直存活着,反复复制开销大
- 可以采用mark-sweep collection
Go对象分配的性能问题
内存分配:为对象在heap上分配内存
- 系统调用mmap()向OS申请一大块内存,例如4MB。
- 将内存划分成大块,例如8KB,称为mspan。
- 再将大块内存分化成特定的小块,用于对象分配。
- noscan mspan:分配不包含指针的对象——GC不需要扫描。
- scan mspan:分配包含指针的对象——GC需要扫描。
- 根据对象的大小,选择合适的块返回。
- 分配路径过长
- 小对象居多
解决方法
使用Balanced GC:指针碰撞风格的对象分配
每个goroutine都绑定一大块内存(1KB),称为gorountine allocation buffer,用于noscan类型 的小对象分配:<128B 维护三个指针:base,end,top。
- 本质,将多次对象分配合并成一次对象分配。
当GAB总大小超过阈值,将GAB中存活的对象复制到另外分配的GAB中,本质是使用了copying GC算法管理小对象