> ## Content Index
> Fetch the complete content index at: https://huizhou92.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# Decrypt Go: runtime.SetFinalizer
- URL: https://huizhou92.com/decrypt-go-runtime-setfinalizer-2/
- Published: 2024-09-06T01:04:12.000Z
- Updated: 2026-09-08T02:30:26.000Z
- Description: Decrypt Go: runtime.SetFinalizer. If we want to do some resource release before an object is GC, we can use returns.SetFinalizer . It’s like executing defe。
- Author: huizhou92
- Tags: #Migrated-1788833207488, #Import 2026-09-08 02:07

If we want to do some resource release before an object is GC, we can use `returns.SetFinalizer`. It’s like executing defer to free resources before a function returns. For example：

**List 1: Using `runtime.SetFinalizer`**

```go
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\n", x.Name)   
     })   
     runtime.GC()   
     time.Sleep(1 * time.Second)   
     runtime.GC()   
 }
```

The [official documentation](https://pkg.go.dev/runtime?ref=huizhou92.com#SetFinalizer) explains that `SetFinalizer` associates a finalizer function with an object. When the garbage collector (GC) detects that an unreachable object has an associated finalizer, it will execute the finalizer and disassociate it. The object will be collected on the next GC cycle if it is unreachable and no longer has an associated finalizer.

### Important Considerations

While `runtime.SetFinalizer` can be helpful, there are a few critical points to keep in mind:

- **Deferred Execution:** The `SetFinalizer` function will not execute until the object is selected for garbage collection. Therefore, avoid using `SetFinalizer` for actions like flushing in-memory content to disk.
- **Extended Object Lifecycle:** `SetFinalizer` can inadvertently extend an object's lifecycle. The finalizer function executes during the first GC cycle, and the target object may become reachable again, delaying its final destruction. This can be problematic in high-concurrency algorithms with numerous object allocations.
- **Memory Leaks with Cyclic References:** Using `runtime.SetFinalizer`, in conjunction with cyclic references, can lead to memory leaks.

**List 2: Memory Leak with `runtime.SetFinalizer`**

```go
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\n", x.Name)   
     })   
     time.Sleep(time.Second)   
     runtime.GC()   
     time.Sleep(time.Second)  
     runtime.GC()  
 }
```

In this code, the object `x` will never be released. The correct approach explicitly removes the finalizer when the object is no longer needed: `runtime.SetFinalizer(&x, nil)`.

### Practical Applications

While `runtime.SetFinalizer` is rarely used in business code (I've never used it); it is more commonly employed within the Go source code itself. For instance, consider the following usage in the [net/http](https://go.googlesource.com/go/blob/aeac0b6cbfb42bc9c9301913a191bb09454d316a/src/net/fd%5Ffake.go?ref=huizhou92.com#L104) package:

```go
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](https://github.com/patrickmn/go-cache/blob/46f407853014144407b6c2ec7ccc76bf67958d93/cache.go?ref=huizhou92.com#L1123) also demonstrates a use case for `SetFinalizer`:

```go
func New(defaultExpiration, cleanupInterval time.Duration) *Cache { 
   items := make(map[string]Item) 
   return newCacheWithJanitor(defaultExpiration, cleanupInterval, items) 
 } 
 ​ 
 func newCacheWithJanitor(de time.Duration, ci time.Duration, m map[string]Item) *Cache { 
   c := newCache(de, m) 
   C := &Cache{c} 
   if ci > 0 { 
     runJanitor(c, ci) 
     runtime.SetFinalizer(C, stopJanitor) 
   } 
   return C 
 } 
 ​ 
 func runJanitor(c *cache, ci time.Duration) { 
   j := &janitor{ 
     Interval: ci, 
     stop:     make(chan bool), 
   } 
   c.janitor = j 
   go j.Run(c) 
 } 
 ​ 
 func stopJanitor(c *Cache) { 
   c.janitor.stop <- true 
 } 
 ​ 
 func (j *janitor) Run(c *cache) { 
   ticker := time.NewTicker(j.Interval) 
   for { 
     select { 
     case <-ticker.C: 
       c.DeleteExpired() 
     case <-j.stop: 
       ticker.Stop() 
       return 
     } 
   } 
 }
```

In `newCacheWithJanitor`, when the `ci` parameter is greater than 0, a background goroutine is started to clean up expired cache entries periodically via a ticker. The asynchronous goroutine exits once a value is read from the stop channel.

The `stopJanitor` function defines a finalizer for the `Cache` pointer `C`. When there are no more references to `Cache` in the business code, the `GC` process triggers the `stopJanitor` function, which writes a value to the internal `stop` channel. This notifies the asynchronous cleanup goroutine to exit, providing a graceful and business-agnostic way to reclaim resources.