finalizer是与对象关联的一个函数,通过runtime.SetFinalizer
来设置,它在对象被GC的时候,这个finalizer会被调用,以完成对象生命中最后一程。由于finalizer的存在,导致了对象在三色标记中,不可能被标为白色对象,也就是垃圾,所以,这个对象的生命也会得以延续一个GC周期。正如defer一样,我们也可以通过 Finalizer 完成一些类似于资源释放的操作
结构概览
heap
1 | type mspan struct { |
special
1 | type special struct { |
##specialfinalizer
1 | type specialfinalizer struct { |
finalizer
1 | type finalizer struct { |
全局变量
1 | var finlock mutex // protects the following variables |
源码分析
创建finalizer
main
1 | func main() { |
SetFinalizer
根据 数据对象 ,生成一个special对象,并绑定到 数据对象 所在的span,串联到span.specials上,并且确保fing的存在
1 | func SetFinalizer(obj interface{}, finalizer interface{}) { |
这里逻辑没什么复杂的,只是在参数、类型的判断等上面,比较的麻烦
removefinalizer
通过removespecial,找到数据对象p所对应的special对象,如果找到的话,释放mheap上对应的内存
1 | func removefinalizer(p unsafe.Pointer) { |
这里的函数,虽然叫removefinalizer, 但是这里暂时跟finalizer结构体没有关系,都是在跟special结构体打交道,后面的addfinalizer也是一样的
removespecial
遍历数据所在的span的specials,如果找到了指定数据p的special的话,就从specials中移除,并返回
1 | func removespecial(p unsafe.Pointer, kind uint8) *special { |
addfinalizer
正好跟removefinalizer相反,这个就是根据数据对象p,创建对应的special,然后添加到span.specials链表上面
1 | func addfinalizer(p unsafe.Pointer, f *funcval, nret uintptr, fint *_type, ot *ptrtype) bool { |
addspecial
这里是添加special的主逻辑
1 | func addspecial(p unsafe.Pointer, s *special) bool { |
createfing
这个函数是保证,创建了finalizer之后,有一个goroutine去运行,这里只运行一次,这个goroutine会由全局变量 fing 记录
1 | func createfing() { |
执行finalizer
在上面的 createfing
的会尝试创建一个goroutine去执行,接下来就分析一下执行流程吧
1 | func runfinq() { |
看完上面的流程的时候,突然发现有点懵逼
- 全局队列finq中是什么时候被插入数据 finalizer的?
- g如果休眠了,那怎么被唤醒呢?
先针对第一个问题分析:
插入队列的操作,要追溯到我们之前分析的GC 深入理解Go-垃圾回收机制 了,在sweep
中有下面一段函数
sweep
1 | func (s *mspan) sweep(preserve bool) bool { |
freespecial
在gc的时候,不仅要把special对应的内存释放掉,而且把specials整理创建对应dinalizer对象,并插入到 finq队列里面
1 | func freespecial(s *special, p unsafe.Pointer, size uintptr) { |
queuefinalizer
1 | func queuefinalizer(p unsafe.Pointer, fn *funcval, nret uintptr, fint *_type, ot *ptrtype) { |
至此,也就明白了,runq全局队列是怎么被填充的了
那么,第二个问题,当fing被休眠后,怎么被唤醒呢?
这里就需要追溯到,深入理解Go-goroutine的实现及Scheduler分析 这篇文章了
findrunnable
在 findrunnable 中有一段代码如下:
1 | func findrunnable() (gp *g, inheritTime bool) { |
wakefing
这里不仅会对状态位 fingwait fingwake做二次判断,而且,如果状态位符合唤醒要求的话,需要重置两个状态位
1 | func wakefing() *g { |
参考文档
- 《Go语言学习笔记》–雨痕