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.
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.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
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.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
