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








