liveBook · Manning

Memory leak

As explained in 100 Go Mistakes and How to Avoid Them

A memory leak is a failure to release memory or another managed resource after it is no longer needed.

A memory leak in Go can mean memory remains in use because a small value still refers to a much larger allocation. It can also result from repeatedly creating resources that remain allocated longer than expected. Check what remains reachable and when a resource is released before assuming that a short slice, substring, or timeout is cheap. section 3.10.2 section 5.6 section 10.2

Three mistakes have different fixes:

  • Reslicing a large slice to keep only a few elements can retain its backing array. If discarded elements contain pointers or pointer fields, their referenced data can remain reachable too. Copy the elements to keep into a new slice, or set the discarded references to nil. Copying iterates over kept elements; clearing iterates over discarded ones, so benchmark when that difference matters. section 3.10.2
  • Storing `log[:36]` as a UUID can retain an entire, much larger log string. With the standard Go compiler, the substring shares its backing data. Store an independent copy, such as `strings.Clone(log[:36])`, when the original log need not remain in memory. section 5.6
  • Calling `time.After(time.Hour)` on every iteration of a busy receive loop creates timeout resources that remain until they expire, even when a message wins the `select`. Use a reusable `time.NewTimer`, or create a timeout context per iteration and cancel it when a message arrives; the context approach also creates resources each iteration. section 10.2

Two elements, a thousand allocations

Suppose each of 1,000 `Foo` values holds a 1 MiB byte slice. Returning `foos[:2]` appears to keep only two values, but a forced garbage collection still leaves the allocation near 1 GiB. Copying the two values into a new slice, or clearing `foos[i].v` for every discarded element, breaks the unwanted retention. section 3.10.2

func main() {
    foos := make([]Foo, 1_000)              #1
    printAlloc()
 
    for i := 0; i < len(foos); i++ {        #2
        foos[i] = Foo{
            v: make([]byte, 1024*1024),
        }
    }
    printAlloc()
 
    two := keepFirstTwoElementsOnly(foos)   #3
    runtime.GC()                            #4
    printAlloc()
    runtime.KeepAlive(two)                  #5
}
 
func keepFirstTwoElementsOnly(foos []Foo) []Foo {
    return foos[:2]
}

The allocation experiment shows the reslice and the explicit garbage-collection check. section 3.10.2

func consumer(ch <-chan Event) {
    timerDuration := 1 * time.Hour
    timer := time.NewTimer(timerDuration)     #1
 
    for {                                     #2
        timer.Reset(timerDuration)            #3
        select {
        case event := <-ch:
            handle(event)
        case <-timer.C:                       #4
            log.Println("warning: no messages received")
        }
    }
}

A single timer replaces the timeout created on every receive-loop iteration. section 10.2

The timer example concerns resources retained until a timeout expires, not necessarily forever. Its allocation estimate—about 200 bytes per `time.After` call—is specifically for Go 1.15. Likewise, substring data sharing describes the standard Go compiler, not a guarantee imposed by the Go specification. section 10.2 section 5.6

How 100 Go Mistakes and How to Avoid Them covers it

How deep it goes
Traces backing-storage and timeout lifetimes through code, then compares copying, clearing references, contexts, and reusable timers.
Examples
Uses a large slice of byte buffers, cached UUIDs extracted from logs, and an hourly timeout in a message loop.
What it assumes
Assumes Go familiarity with slices, strings, channels, and select while briefly explaining substring syntax and time.After behavior.
What you can do after
Diagnose/debug with it by checking retained backing storage and timeout lifetimes, then choosing copies, cleared references, or reusable timers.
How it connects
Connects memory leaks to slices, garbage collection, substrings, channels, contexts, and timers.

Also explained in

Compare how 5 books explain memory leak →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of 100 Go Mistakes and How to Avoid Them. The section links point to the parts of the book it draws on.