A finalizer is a function associated with an object, set by runtime.SetFinalizer, that is called when the object is GC to complete the last step in the object's life.The existence of finalizer makes it impossible for an object to be marked as white in a trichrome marker, i.e. garbage, so that the life of the object can continue for a GC cycle.Just like defer, Finalizer allows us to do something similar to resource release
1. Overview of the structure
1.1. heap
type mspan struct { // A special ly concatenated list of all objects on the current span // There is an offset in the specialty, which is the offset of the data object on the space, through which the data object is associated with the specialty specials *special // linked list of special records sorted by offset. }
1.2. special
type special struct { next *special // linked list in span // offset of data object on space 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. // The size of the return ed data nret uintptr // Type of the first parameter fint *_type // May be a heap pointer, but always live. // Pointer type of data object associated with 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. Global variables
var finlock mutex // protects the following variables // Run finalizer's g, only one g, sleep when not in use, wake up when needed var fing *g // goroutine that runs finalizers // finalizer's global queue, here is the list of finalizers that have been set up var finq *finblock // list of finalizers that are to be executed // The list of finblock s that have been released, cached in finc, can be removed directly when needed, avoiding memory allocation again var finc *finblock // cache of free blocks var finptrmask [_FinBlockSize / sys.PtrSize / 8]byte var fingwait bool // The token bit of fing, which uses fingwait and fingwake to determine if fing needs to be waked up var fingwake bool // Chain List of All blocks var allfin *finblock // list of all blocks
2. Source Code Analysis
2.1. Create finalizer
2.1.1. main
func main() { // i is the data object that follows var i = 3 // Here the func is the finalizer runtime.SetFinalizer(&i, func(i *int) { fmt.Println(i, *i, "set finalizer") }) time.Sleep(time.Second * 5) }
2.1.2. SetFinalizer
Generate a special object from the data object, bind it to the span in which the data object resides, concatenate it to span.specials, and ensure fing exists
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 // -- Omit the logic of data validation-- ot := (*ptrtype)(unsafe.Pointer(etyp)) // find the containing object // base==0 when no allocated address is found in memory, setFinalizer is called when memory is reclaimed, and will not be reclaimed without allocation base, _, _ := findObject(uintptr(e.data), 0, 0) f := efaceOf(&finalizer) ftyp := f._type // If finalizer type == nil, try removing (if not, you don't need to) if ftyp == nil { // switch to system stack and remove finalizer systemstack(func() { removefinalizer(e.data) }) return } // --Verify the number and type of finalizer parameters-- 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 // Calculate and align the size of the 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 // Make sure finalizer has a goroutine createfing() systemstack(func() { // Instead, switch to g0, add finalizer, and do not repeat the settings if !addfinalizer(e.data, (*funcval)(f.data), nret, fint, ot) { throw("runtime.SetFinalizer: finalizer already set") } }) }
There is nothing complicated in the logic here, but the difficulty in comparing parameters, types, etc.
2.1.3. removefinalizer
Through removespecial, find the special object corresponding to data object p, if found, release the corresponding memory on mheap
func removefinalizer(p unsafe.Pointer) { // Find the corresponding special object based on data p s := (*specialfinalizer)(unsafe.Pointer(removespecial(p, _KindSpecialFinalizer))) if s == nil { return // there wasn't a finalizer to remove } lock(&mheap_.speciallock) // Release the memory corresponding to the special s found mheap_.specialfinalizeralloc.free(unsafe.Pointer(s)) unlock(&mheap_.speciallock) }
The function here, although called removefinalizer, has nothing to do with the finalizer structure for the time being, it is all dealing with the special structure, and so is the addfinalizer after it.
2.1.4. removespecial
Traverses through the specials of the span in which the data resides, removes the specials from the specified data p, and returns
func removespecial(p unsafe.Pointer, kind uint8) *special { // Find the span where data p is located 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() // Make sure the space is cleaned span.ensureSwept() // Get the offset of the data P and find the corresponding special p according to the offset offset := uintptr(p) - span.base() lock(&span.speciallock) t := &span.specials // Traverse the span.specials list 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 { // Found, modify pointer, remove currently found Specials *t = s.next unlock(&span.speciallock) releasem(mp) return s } t = &s.next } unlock(&span.speciallock) releasem(mp) // If not found, return to nil return nil }
2.1.5. addfinalizer
As opposed to removefinalizer, this is to create a corresponding special based on the data object p and add it to the span.specials list
func addfinalizer(p unsafe.Pointer, f *funcval, nret uintptr, fint *_type, ot *ptrtype) bool { lock(&mheap_.speciallock) // Allocate a block of memory for finalizer to use 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 // Not added successfully because p already has a special object lock(&mheap_.speciallock) mheap_.specialfinalizeralloc.free(unsafe.Pointer(s)) unlock(&mheap_.speciallock) return false }
2.1.6. addspecial
Here is the main logic for adding Specials
func addspecial(p unsafe.Pointer, s *special) bool { span := spanOfHeap(uintptr(p)) if span == nil { throw("addspecial on invalid pointer") } // As with removerspecial, make sure the space has been cleaned 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 { // Already exists, can't add, a data object, can only bind a 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. // Add to end of specials queue s.offset = uint16(offset) s.next = *t *t = s unlock(&span.speciallock) releasem(mp) return true }
2.1.7. createfing
This function guarantees that after the finalizer is created, a goroutine runs, which runs only once and is recorded by the global variable fing
func createfing() { // start the finalizer goroutine exactly once // Go ahead and create a goroutine to monitor the operation at all times if fingCreate == 0 && atomic.Cas(&fingCreate, 0, 1) { // Open a goroutine run go runfinq() } }
2.2. Execute finalizer
createfing above tries to create a goroutine to execute, so let's analyze the execution process
func runfinq() { var ( frame unsafe.Pointer framecap uintptr ) for { lock(&finlock) // Get finq global queue and empty global queue fb := finq finq = nil if fb == nil { // If the global queue is empty, sleep on the current g and wait for wakeup gp := getg() fing = gp // Set fing's status flag bit fingwait = true goparkunlock(&finlock, waitReasonFinalizerWait, traceEvGoBlock, 1) continue } unlock(&finlock) // Loop through fin arrays in runq chains for fb != nil { for i := fb.cnt; i > 0; i-- { f := &fb.fin[i-1] // Gets the size of the returned data that stores the current finalizer and, if larger than before, allocates it 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. // Clear frame memory storage *(*[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") } // Call finalizer function 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. // Empty finalizer properties f.fn = nil f.arg = nil f.ot = nil atomic.Store(&fb.cnt, i-1) } // Put completed finalizer s in finc for caching to avoid reallocating memory next := fb.next lock(&finlock) fb.next = finc finc = fb unlock(&finlock) fb = next } } }
When I finished the above process, I suddenly found a little confused
- When was the data finalizer inserted in the global queue finq?
- g If I sleep, how can I be awakened?
Start with the first question:
Inserting the queue goes back to the GC we analyzed earlier Understanding Go-Garbage Recycling Yes, there is a function below in 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 // The entry to the global finq queue is here freespecial(y, unsafe.Pointer(p), size) } .... } .... }
2.2.2. freespecial
When gc, not only do you need to free the memory for the specials, but you also clean up the specials to create the corresponding dinalizer object and insert it into the finq queue
func freespecial(s *special, p unsafe.Pointer, size uintptr) { switch s.kind { case _KindSpecialFinalizer: // Join this finalizer to the global queue 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) // The following two cases are not within the scope of analysis, omitted 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) // If finq is empty or finq's internal array is full, get the block from finc or reassign and insert it into finq's chain header 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)] } } } // Remove and get the chain header from finc block := finc finc = block.next // Mount the list of chains obtained from finc to the queue header of finq, which points to the new block block.next = finq finq = block } // Get the block corresponding to the index from finq.cnt f := &finq.fin[finq.cnt] atomic.Xadd(&finq.cnt, +1) // Sync with markroots // Set related properties f.fn = fn f.nret = nret f.fint = fint f.ot = ot f.arg = p // Set Wake-up Flag fingwake = true unlock(&finlock) }
Now you can see how runq global queues are populated
So, the second question, how can fing wake up when it is dormant?
It goes back here, Deeply Understanding the Implementation of Go-goroutine and Scheduler Analysis This article
2.2.4. findrunnable
There is a piece of code in findrunnable as follows:
func findrunnable() (gp *g, inheritTime bool) { // wakefing to wake fing and return fing if fingwait && fingwake { if gp := wakefing(); gp != nil { // Wake up g and continue from hibernation ready(gp, 0, true) } } }
2.2.5. wakefing
Not only will the state bit fingwait fingwake be judged twice here, but if it meets the wake-up requirements, the two state bits need to be reset
func wakefing() *g { var res *g lock(&finlock) if fingwait && fingwake { fingwait = false fingwake = false res = fing } unlock(&finlock) return res }
3. Reference Documents
- Go Language Learning Notes--Rain Tracks