Why Go Replaces runtime.SetFinalizer with runtime.AddCleanup and How to Use It

This article explains that Go plans to deprecate runtime.SetFinalizer in favor of the newer runtime.AddCleanup, demonstrates how to use SetFinalizer for resource cleanup before garbage collection, highlights common pitfalls such as extended lifetimes and memory leaks, and shows real‑world examples from the standard library and go‑cache.

Radish, Keep Going!
Radish, Keep Going!
Radish, Keep Going!
Why Go Replaces runtime.SetFinalizer with runtime.AddCleanup and How to Use It

Note that this is an old article; Go may deprecate runtime.SetFinalizer and replace it with runtime.AddCleanup, which addresses several pain points. See the original article Go 1.24: runtime.AddCleanup, improving issues with runtime.SetFinalizer .

If you need to release resources before an object is garbage‑collected, you can use runtime.SetFinalizer, similar to a defer. Example:

type MyStruct struct {
    Name  string
    Other *MyStruct
}

func main() {
    x := MyStruct{Name: "X"}
    runtime.SetFinalizer(&x, func(x *MyStruct) {
        fmt.Printf("Finalizer for %s is called
", x.Name)
    })
    runtime.GC()
    time.Sleep(1 * time.Second)
    runtime.GC()
}

The official documentation explains that when the GC finds an unreachable object with an associated finalizer, it runs the finalizer and then clears the association; the next GC will actually reclaim the object.

Key points to watch:

Even if the program ends normally or errors, the finalizer may not run before the object is reclaimed, so avoid performing I/O such as flushing data to disk inside a finalizer.

The biggest issue is that the finalizer extends the object's lifetime: the object becomes reachable again after the first GC, requiring a second GC to truly destroy it, which can be problematic for high‑concurrency allocations.

Pointer cycles combined with runtime.SetFinalizer can cause memory leaks.

Example of a memory‑leak scenario with cyclic references:

// MyStruct is a simple struct containing a pointer field.
type MyStruct struct {
    Name  string
    Other *MyStruct
}

func main() {
    x := MyStruct{Name: "X"}
    y := MyStruct{Name: "Y"}

    x.Other = &y
    y.Other = &x
    runtime.SetFinalizer(&x, func(x *MyStruct) {
        fmt.Printf("Finalizer for %s is called
", x.Name)
    })
    time.Sleep(time.Second)
    runtime.GC()
    time.Sleep(time.Second)
    runtime.GC()
}

In this case x is never released. The correct approach is to explicitly remove the finalizer when the object is no longer needed:

runtime.SetFinalizer(&x, nil)

Practical Application

Business code rarely calls runtime.SetFinalizer, but the Go standard library uses it extensively, for example in net/http:

func (fd *netFD) setAddr(laddr, raddr Addr) {
    fd.laddr = laddr
    fd.raddr = raddr
    runtime.SetFinalizer(fd, (*netFD).Close)
}

func (fd *netFD) Close() error {
    if fd.fakeNetFD != nil {
        return fd.fakeNetFD.Close()
    }
    runtime.SetFinalizer(fd, nil)
    return fd.pfd.Close()
}

The go-cache library also uses a finalizer. When the cleanup interval is greater than zero, it starts a background goroutine that periodically removes expired items. The cache object registers a finalizer runtime.SetFinalizer(C, stopJanitor) so that when the cache becomes unreachable, the GC triggers stopJanitor, which writes to the stop channel, causing the janitor goroutine to exit gracefully.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Memory ManagementGoRuntimeGarbage CollectionAddCleanupSetFinalizer
Radish, Keep Going!
Written by

Radish, Keep Going!

Personal sharing

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.