Understanding Go's Finalizer Mechanism and a Common Pitfall Leading to Memory Leaks

The article explains Go’s runtime finalizer mechanism, shows how a single blocked finalizer can stall all cleanup callbacks, and recounts a real‑world memory‑leak incident where an unrelated goroutine’s blocked finalizer for struct B prevented struct A’s resources from being released.

Sohu Tech Products
Sohu Tech Products
Sohu Tech Products
Understanding Go's Finalizer Mechanism and a Common Pitfall Leading to Memory Leaks

The article shares a practical Go GC-related pitfall: a component in production suffered occasional OOM due to a memory leak. Using Go's pprof tool, the leak was quickly traced to a data structure A whose resources were supposed to be released via Go's Finalizer mechanism. However, despite careful code review, the leak persisted.

Days later, the breakthrough came from an apparently unrelated blocked goroutine. The blocked goroutine was part of the release process of another struct B, which also relied on the Finalizer mechanism. This unrelated-looking issue turned out to be the root cause of the leak.

What is Go's Finalizer mechanism? In Go, programmers allocate memory while the runtime's garbage collector reclaims it. The runtime provides runtime.SetFinalizer, allowing a programmer to register a callback that is invoked automatically when the GC is about to collect a particular object.

Signature: func SetFinalizer(obj interface{}, finalizer interface{}) Example demonstrating a finalizer on a simple struct:

type TestStruct struct {
    name string
}

//go:noinline
func newTestStruct() *TestStruct {
    v := &TestStruct{"n1"}
    runtime.SetFinalizer(v, func(p *TestStruct) {
        fmt.Println("gc Finalizer")
    })
    return v
}

func main() {
    t := newTestStruct()
    fmt.Println("== start ===")
    _ = t
    fmt.Println("== ... ===")
    runtime.GC()
    fmt.Println("== end ===")
}

The example sets a finalizer that prints a message when the object is reclaimed. By invoking runtime.GC() manually, you can observe the finalizer execution.

While finalizers are powerful, they have important constraints. The Go runtime runs all finalizers in a single goroutine, sequentially. The official documentation states:

A single goroutine runs all finalizers for a program, sequentially. If a finalizer must run for a long time, it should do so by starting a new goroutine.

This means that if one finalizer blocks or panics, it can stall the execution of all subsequent finalizers, potentially causing resource leaks.

Illustrative minimal example where one finalizer blocks and prevents others from running:

var (
    done chan struct{}
)

type A struct { name string }

type B struct { name string }

type C struct { name string }

func newA() *A {
    v := &A{"n1"}
    runtime.SetFinalizer(v, func(p *A) { fmt.Println("gc Finalizer A") })
    return v
}

func newB() *B {
    v := &B{"n1"}
    runtime.SetFinalizer(v, func(p *B) {
        <-done // blocks forever
        fmt.Println("gc Finalizer B")
    })
    return v
}

func newC() *C {
    v := &C{"n1"}
    runtime.SetFinalizer(v, func(p *C) { fmt.Println("gc Finalizer C") })
    return v
}

func main() {
    a := newA()
    b := newB()
    c := newC()
    fmt.Println("== start ===")
    _ = a; _ = b; _ = c
    fmt.Println("== ... ===")
    for i := 0; i < 10; i++ { runtime.GC() }
    fmt.Println("== end ===")
}

In this code, the finalizer for B blocks on a channel read, which prevents the finalizers for A and C from executing, leading to the observed memory leak.

Summary

Go's Finalizer mechanism allows programmers to register cleanup callbacks, helping manage resource lifetimes safely.

All finalizers run in a single, sequential goroutine; a blocked or slow finalizer can halt the entire finalizer system.

When diagnosing memory issues, the root cause may reside in an unexpected corner of the codebase, requiring a broader investigative mindset.

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.

concurrencyGoRuntimememory leakgcFinalizer
Sohu Tech Products
Written by

Sohu Tech Products

A knowledge-sharing platform for Sohu's technology products. As a leading Chinese internet brand with media, video, search, and gaming services and over 700 million users, Sohu continuously drives tech innovation and practice. We’ll share practical insights and tech news here.

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.