finalizer是与对象关联的一个函数,通过runtime.SetFinalizer 来设置,它在对象被GC的时候,这个finalizer会被调用,以完成对象生命中最后一程。由于finalizer的存在,导致了对象在三色标记中,不可能被标为白色对象,也就是垃圾,所以,这个对象的生命也会得以延续一个GC周期。正如defer一样,我们也可以通过 Finalizer 完成一些类似于资源释放的操作

1. 结构概览

1.1. heap

type mspan struct {    // 当前span上所有对象的special串成链表    // special中有个offset,就是数据对象在span上的offset,通过offset,将数据对象和special关联起来    specials    *special   // linked list of special records sorted by offset.}

1.2. special

type special struct {    next   *special // linked list in span    // 数据对象在span上的offset    offset uint16   // span offset of object    kind   byte     // kind of special}

1.3. specialfinalizer

type specialfinalizer struct {    special special    fn      *funcval // May be a heap pointer.    // return的数据的大小    nret    uintptr    // 第一个参数的类型    fint    *_type   // May be a heap pointer, but always live.    // 与finalizer关联的数据对象的指针类型    ot      *ptrtype // May be a heap pointer, but always live.}

1.4. finalizer

type finalizer struct {    fn   *funcval       // function to call (may be a heap pointer)    arg  unsafe.Pointer // ptr to object (may be a heap pointer)    nret uintptr        // bytes of return values from fn    fint *_type         // type of first argument of fn    ot   *ptrtype       // type of ptr to object (may be a heap pointer)}

1.5. 全局变量

var finlock mutex  // protects the following variables// 运行finalizer的g,只有一个g,不用的时候休眠,需要的时候再唤醒var fing *g        // goroutine that runs finalizers// finalizer的全局队列,这里是已经设置的finalizer串成的链表var finq *finblock // list of finalizers that are to be executed// 已经释放的finblock的链表,用finc缓存起来,以后需要使用的时候可以直接取走,避免再走一遍内存分配了var finc *finblock // cache of free blocksvar finptrmask [_FinBlockSize / sys.PtrSize / 8]bytevar fingwait bool  // fing的标志位,通过 fingwait和fingwake,来确定是否需要唤醒fingvar fingwake bool// 所有的blocks串成的链表var allfin *finblock // list of all blocks

2. 源码分析

2.1. 创建finalizer

2.1.1. main

func main() {    // i 就是后面说的 数据对象    var i = 3    // 这里的func 就是后面一直说的 finalizer    runtime.SetFinalizer(&i, func(i *int) {        fmt.Println(i, *i, "set finalizer")    })    time.Sleep(time.Second * 5)}

2.1.2. SetFinalizer

根据 数据对象 ,生成一个special对象,并绑定到 数据对象 所在的span,串联到span.specials上,并且确保fing的存在

func SetFinalizer(obj interface{}, finalizer interface{}) {    if debug.sbrk != 0 {        // debug.sbrk never frees memory, so no finalizers run        // (and we don't have the data structures to record them).        return    }    e := efaceOf(&obj)    etyp := e._type    // ---- 省略数据校验的逻辑 ---    ot := (*ptrtype)(unsafe.Pointer(etyp))    // find the containing object    // 在内存中找不到分配的地址时 base==0,setFinalizer 是在内存回收的时候调用,没有分配就不会回收    base, _, _ := findObject(uintptr(e.data), 0, 0)    f := efaceOf(&finalizer)    ftyp := f._type    // 如果 finalizer type == nil,尝试移除(没有的话,就不需要移除了)    if ftyp == nil {        // switch to system stack and remove finalizer        systemstack(func() {            removefinalizer(e.data)        })        return    }    // --- 对finalizer参数数量及类型进行校验 --    if ftyp.kind&kindMask != kindFunc {        throw("runtime.SetFinalizer: second argument is " + ftyp.string() + ", not a function")    }    ft := (*functype)(unsafe.Pointer(ftyp))    if ft.dotdotdot() {        throw("runtime.SetFinalizer: cannot pass " + etyp.string() + " to finalizer " + ftyp.string() + " because dotdotdot")    }    if ft.inCount != 1 {        throw("runtime.SetFinalizer: cannot pass " + etyp.string() + " to finalizer " + ftyp.string())    }    fint := ft.in()[0]    switch {    case fint == etyp:        // ok - same type        goto okarg    case fint.kind&kindMask == kindPtr:        if (fint.uncommon() == nil || etyp.uncommon() == nil) && (*ptrtype)(unsafe.Pointer(fint)).elem == ot.elem {            // ok - not same type, but both pointers,            // one or the other is unnamed, and same element type, so assignable.            goto okarg        }    case fint.kind&kindMask == kindInterface:        ityp := (*interfacetype)(unsafe.Pointer(fint))        if len(ityp.mhdr) == 0 {            // ok - satisfies empty interface            goto okarg        }        if _, ok := assertE2I2(ityp, *efaceOf(&obj)); ok {            goto okarg        }    }    throw("runtime.SetFinalizer: cannot pass " + etyp.string() + " to finalizer " + ftyp.string())okarg:    // compute size needed for return parameters    // 计算返回参数的大小并进行对齐    nret := uintptr(0)    for _, t := range ft.out() {        nret = round(nret, uintptr(t.align)) + uintptr(t.size)    }    nret = round(nret, sys.PtrSize)    // make sure we have a finalizer goroutine    // 确保 finalizer 有一个 goroutine    createfing()    systemstack(func() {        // 却换到g0,添加finalizer,并且不能重复设置        if !addfinalizer(e.data, (*funcval)(f.data), nret, fint, ot) {            throw("runtime.SetFinalizer: finalizer already set")        }    })}

这里逻辑没什么复杂的,只是在参数、类型的判断等上面,比较的麻烦

2.1.3. removefinalizer

通过removespecial,找到数据对象p所对应的special对象,如果找到的话,释放mheap上对应的内存

func removefinalizer(p unsafe.Pointer) {    // 根据数据p找到对应的special对象    s := (*specialfinalizer)(unsafe.Pointer(removespecial(p, _KindSpecialFinalizer)))    if s == nil {        return // there wasn't a finalizer to remove    }    lock(&mheap_.speciallock)    // 释放找到的special所对应的内存    mheap_.specialfinalizeralloc.free(unsafe.Pointer(s))    unlock(&mheap_.speciallock)}

这里的函数,虽然叫removefinalizer, 但是这里暂时跟finalizer结构体没有关系,都是在跟special结构体打交道,后面的addfinalizer也是一样的

2.1.4. removespecial

遍历数据所在的span的specials,如果找到了指定数据p的special的话,就从specials中移除,并返回

func removespecial(p unsafe.Pointer, kind uint8) *special {    // 找到数据p所在的span    span := spanOfHeap(uintptr(p))    if span == nil {        throw("removespecial on invalid pointer")    }    // Ensure that the span is swept.    // Sweeping accesses the specials list w/o locks, so we have    // to synchronize with it. And it's just much safer.    mp := acquirem()    // 保证span被清扫过了    span.ensureSwept()    // 获取数据p的偏移量,根据偏移量去寻找p对应的special    offset := uintptr(p) - span.base()    lock(&span.speciallock)    t := &span.specials    // 遍历span.specials这个链表    for {        s := *t        if s == nil {            break        }        // This function is used for finalizers only, so we don't check for        // "interior" specials (p must be exactly equal to s->offset).        if offset == uintptr(s.offset) && kind == s.kind {            // 找到了,修改指针,将当前找到的special移除            *t = s.next            unlock(&span.speciallock)            releasem(mp)            return s        }        t = &s.next    }    unlock(&span.speciallock)    releasem(mp)    // 没有找到,就返回nil    return nil}

2.1.5. addfinalizer

正好跟removefinalizer相反,这个就是根据数据对象p,创建对应的special,然后添加到span.specials链表上面

func addfinalizer(p unsafe.Pointer, f *funcval, nret uintptr, fint *_type, ot *ptrtype) bool {    lock(&mheap_.speciallock)    // 分配出来一块内存供finalizer使用    s := (*specialfinalizer)(mheap_.specialfinalizeralloc.alloc())    unlock(&mheap_.speciallock)    s.special.kind = _KindSpecialFinalizer    s.fn = f    s.nret = nret    s.fint = fint    s.ot = ot    if addspecial(p, &s.special) {        return true    }    // There was an old finalizer    // 没有添加成功,是因为p已经有了一个special对象了    lock(&mheap_.speciallock)    mheap_.specialfinalizeralloc.free(unsafe.Pointer(s))    unlock(&mheap_.speciallock)    return false}

2.1.6. addspecial

这里是添加special的主逻辑

func addspecial(p unsafe.Pointer, s *special) bool {    span := spanOfHeap(uintptr(p))    if span == nil {        throw("addspecial on invalid pointer")    }    // 同 removerspecial一样,确保这个span已经清扫过了    mp := acquirem()    span.ensureSwept()    offset := uintptr(p) - span.base()    kind := s.kind    lock(&span.speciallock)    // Find splice point, check for existing record.    t := &span.specials    for {        x := *t        if x == nil {            break        }        if offset == uintptr(x.offset) && kind == x.kind {            // 已经存在了,不能在增加了,一个数据对象,只能绑定一个finalizer            unlock(&span.speciallock)            releasem(mp)            return false // already exists        }        if offset < uintptr(x.offset) || (offset == uintptr(x.offset) && kind < x.kind) {            break        }        t = &x.next    }    // Splice in record, fill in offset.    // 添加到 specials 队列尾    s.offset = uint16(offset)    s.next = *t    *t = s    unlock(&span.speciallock)    releasem(mp)    return true}

2.1.7. createfing

这个函数是保证,创建了finalizer之后,有一个goroutine去运行,这里只运行一次,这个goroutine会由全局变量 fing 记录

func createfing() {    // start the finalizer goroutine exactly once    // 进创建一个goroutine,进行时刻监控运行    if fingCreate == 0 && atomic.Cas(&fingCreate, 0, 1) {        // 开启一个goroutine运行        go runfinq()    }}

2.2. 执行finalizer

在上面的 createfing 的会尝试创建一个goroutine去执行,接下来就分析一下执行流程吧

func runfinq() {    var (        frame    unsafe.Pointer        framecap uintptr    )    for {        lock(&finlock)        // 获取finq 全局队列,并清空全局队列        fb := finq        finq = nil        if fb == nil {            // 如果全局队列为空,休眠当前g,等待被唤醒            gp := getg()            fing = gp            // 设置fing的状态标志位            fingwait = true            goparkunlock(&finlock, waitReasonFinalizerWait, traceEvGoBlock, 1)            continue        }        unlock(&finlock)        // 循环执行runq链表里的fin数组        for fb != nil {            for i := fb.cnt; i > 0; i-- {                f := &fb.fin[i-1]                // 获取存储当前finalizer的返回数据的大小,如果比之前大,则分配                framesz := unsafe.Sizeof((interface{})(nil)) + f.nret                if framecap < framesz {                    // The frame does not contain pointers interesting for GC,                    // all not yet finalized objects are stored in finq.                    // If we do not mark it as FlagNoScan,                    // the last finalized object is not collected.                    frame = mallocgc(framesz, nil, true)                    framecap = framesz                }                if f.fint == nil {                    throw("missing type in runfinq")                }                // frame is effectively uninitialized                // memory. That means we have to clear                // it before writing to it to avoid                // confusing the write barrier.                // 清空frame内存存储                *(*[2]uintptr)(frame) = [2]uintptr{}                switch f.fint.kind & kindMask {                case kindPtr:                    // direct use of pointer                    *(*unsafe.Pointer)(frame) = f.arg                case kindInterface:                    ityp := (*interfacetype)(unsafe.Pointer(f.fint))                    // set up with empty interface                    (*eface)(frame)._type = &f.ot.typ                    (*eface)(frame).data = f.arg                    if len(ityp.mhdr) != 0 {                        // convert to interface with methods                        // this conversion is guaranteed to succeed - we checked in SetFinalizer                        *(*iface)(frame) = assertE2I(ityp, *(*eface)(frame))                    }                default:                    throw("bad kind in runfinq")                }                // 调用finalizer函数                fingRunning = true                reflectcall(nil, unsafe.Pointer(f.fn), frame, uint32(framesz), uint32(framesz))                fingRunning = false                // Drop finalizer queue heap references                // before hiding them from markroot.                // This also ensures these will be                // clear if we reuse the finalizer.                // 清空finalizer的属性                f.fn = nil                f.arg = nil                f.ot = nil                atomic.Store(&fb.cnt, i-1)            }            // 将已经完成的finalizer放入finc以作缓存,避免再次分配内存            next := fb.next            lock(&finlock)            fb.next = finc            finc = fb            unlock(&finlock)            fb = next        }    }}

看完上面的流程的时候,突然发现有点懵逼

  1. 全局队列finq中是什么时候被插入数据 finalizer的?
  2. g如果休眠了,那怎么被唤醒呢?

先针对第一个问题分析:

插入队列的操作,要追溯到我们之前分析的GC 深入理解Go-垃圾回收机制 了,在sweep 中有下面一段函数

2.2.1. sweep

func (s *mspan) sweep(preserve bool) bool {    ....    specialp := &s.specials    special := *specialp    for special != nil {        ....        if special.kind == _KindSpecialFinalizer || !hasFin {            // Splice out special record.            y := special            special = special.next            *specialp = special            // 加入全局finq队列的入口就在这里了            freespecial(y, unsafe.Pointer(p), size)        }        ....    }    ....}

2.2.2. freespecial

在gc的时候,不仅要把special对应的内存释放掉,而且把specials整理创建对应dinalizer对象,并插入到 finq队列里面

func freespecial(s *special, p unsafe.Pointer, size uintptr) {    switch s.kind {    case _KindSpecialFinalizer:        // 把这个finalizer加入到全局队列        sf := (*specialfinalizer)(unsafe.Pointer(s))        queuefinalizer(p, sf.fn, sf.nret, sf.fint, sf.ot)        lock(&mheap_.speciallock)        mheap_.specialfinalizeralloc.free(unsafe.Pointer(sf))        unlock(&mheap_.speciallock)    // 下面两种情况不在分析范围内,省略    case _KindSpecialProfile:        sp := (*specialprofile)(unsafe.Pointer(s))        mProf_Free(sp.b, size)        lock(&mheap_.speciallock)        mheap_.specialprofilealloc.free(unsafe.Pointer(sp))        unlock(&mheap_.speciallock)    default:        throw("bad special kind")        panic("not reached")    }}

2.2.3. queuefinalizer

func queuefinalizer(p unsafe.Pointer, fn *funcval, nret uintptr, fint *_type, ot *ptrtype) {    lock(&finlock)    // 如果finq为空或finq的内部数组已经满了,则从finc或重新分配 来获取block并插入到finq的链表头    if finq == nil || finq.cnt == uint32(len(finq.fin)) {        if finc == nil {            finc = (*finblock)(persistentalloc(_FinBlockSize, 0, &memstats.gc_sys))            finc.alllink = allfin            allfin = finc            if finptrmask[0] == 0 {                // Build pointer mask for Finalizer array in block.                // Check assumptions made in finalizer1 array above.                if (unsafe.Sizeof(finalizer{}) != 5*sys.PtrSize ||                    unsafe.Offsetof(finalizer{}.fn) != 0 ||                    unsafe.Offsetof(finalizer{}.arg) != sys.PtrSize ||                    unsafe.Offsetof(finalizer{}.nret) != 2*sys.PtrSize ||                    unsafe.Offsetof(finalizer{}.fint) != 3*sys.PtrSize ||                    unsafe.Offsetof(finalizer{}.ot) != 4*sys.PtrSize) {                    throw("finalizer out of sync")                }                for i := range finptrmask {                    finptrmask[i] = finalizer1[i%len(finalizer1)]                }            }        }        // 从finc中移除并获取链表头        block := finc        finc = block.next        // 将从finc获取到的链表挂载到finq的队列头,finq指向新的block        block.next = finq        finq = block    }    // 根据finq.cnt获取索引对应的block    f := &finq.fin[finq.cnt]    atomic.Xadd(&finq.cnt, +1) // Sync with markroots    // 设置相关属性    f.fn = fn    f.nret = nret    f.fint = fint    f.ot = ot    f.arg = p    // 设置唤醒标志    fingwake = true    unlock(&finlock)}

至此,也就明白了,runq全局队列是怎么被填充的了

那么,第二个问题,当fing被休眠后,怎么被唤醒呢?

这里就需要追溯到,深入理解Go-goroutine的实现及Scheduler分析 这篇文章了

2.2.4. findrunnable

在 findrunnable 中有一段代码如下:

func findrunnable() (gp *g, inheritTime bool) {    // 通过状态位判断是否需要唤醒 fing, 通过wakefing来判断并返回fing    if fingwait && fingwake {        if gp := wakefing(); gp != nil {            // 唤醒g,并从休眠出继续执行            ready(gp, 0, true)        }    }}

2.2.5. wakefing

这里不仅会对状态位 fingwait fingwake做二次判断,而且,如果状态位符合唤醒要求的话,需要重置两个状态位

func wakefing() *g {    var res *g    lock(&finlock)    if fingwait && fingwake {        fingwait = false        fingwake = false        res = fing    }    unlock(&finlock)    return res}

3. 参考文档

  • 《Go语言学习笔记》--雨痕