liveBook · Manning

Garbage collection

As explained in 100 Go Mistakes and How to Avoid Them

Garbage collection is automatic runtime management that identifies and reclaims memory no longer reachable by a program.

A common mistake is to expect a forced garbage-collection cycle to make heap usage return to its starting level. Go’s garbage collector reclaims heap allocations that are no longer referenced; it cannot reclaim memory still retained by a slice or map. Heap allocations also cost more than stack allocations and rely on the collector for cleanup. section 12.9 section 3.10.2 section 3.12 section 12.11

Go uses concurrent mark-and-sweep collection: it identifies objects still in use and reclaims unreferenced allocations. Much of a cycle runs alongside application code, but the cycle includes brief stop-the-world periods. A periodic scavenger can later return unneeded memory to the operating system. section 12.9.1

When memory remains high after collection, check what is retained:

  • A two-element slice may still retain a 1,000-element backing array. If the elements contain slices or other pointer fields, allocations associated with the other 998 elements remain. Copy the two elements into a new slice, or clear the discarded elements’ pointer fields when reusing the backing array. section 3.10.2
  • Deleting map entries does not shrink the map’s buckets. In a million-entry example, collection reduced measured heap usage from 461 MB to 293 MB, not back to zero. Storing pointers to large values reduced the post-deletion measurement to 38 MB in the comparison shown. section 3.12
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 returned `foos[:2]` still uses the original backing array. The explicit collection and `runtime.KeepAlive(two)` make its retention visible in the allocation measurement. section 3.10.2

`GOGC` controls when the next cycle is triggered based on heap growth since the previous cycle. At its default value of 100, a heap of 128 MB at one collection reaches the next trigger at 256 MB. A sudden load increase can cause frequent cycles and latency; a higher setting may reduce that pressure, but a larger heap takes longer to clean. Test changes against production-like load rather than treating a higher value as an automatic improvement. section 12.9.1 section 12.9.2

How 100 Go Mistakes and How to Avoid Them covers it

How deep it goes
Explains concurrent mark-and-sweep, pauses, scavenging, and GOGC triggers, then works through memory-retention and load scenarios.
Examples
Compares allocation measurements after shortening a slice or emptying a million-entry map, and contrasts steady with sudden user growth.
What it assumes
Assumes familiarity with Go slices, maps, and heap allocations while explaining collector behavior from its basic purpose.
What you can do after
Diagnose/debug retained heap memory and operate/configure GOGC cautiously when collection frequency affects latency.
How it connects
Connects collection to heap allocations, slice backing arrays, map buckets, and application latency.

Also explained in

Compare how 9 books explain garbage collection →

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.