What does this print, and what memory problem does it illustrate?
Question 358MediumGo 1.22 to 1.25
func header(data []byte) []byte {
return data[:4]
}
func main() {
big := make([]byte, 10<<20) // 10 MiB
h := header(big)
big = nil
runtime.GC()
fmt.Println(len(h), cap(h))
}
Output: 4 10485760. The 4-byte slice still refers to the entire 10 MiB backing array, so none of it can be freed. The GC keeps whole allocations alive, not parts of them. This is the "sub-slice retention" leak, and it often happens when parsing a header or a few IDs out of large network buffers and storing them in a long-lived map.
The same applies to substrings: s[:10] of a 1 MB string keeps the whole 1 MB alive.
// Fixes: copy the small part out
func headerFixed(data []byte) []byte {
return bytes.Clone(data[:4]) // or: append([]byte(nil), data[:4]...)
}
func idFromLine(line string) string {
return strings.Clone(line[:8]) // Go 1.20+
}
Gotcha: the full slice expression data[:4:4] limits the capacity, so appends to the result cannot overwrite data, but it does not free the rest of the array. You need an actual copy. Use pprof's inuse_space to find where the retained memory was allocated.
More on Memory, GC & Runtime Internals
- Q356What are weak pointers in Go and when would you use them?
- Q357What does
runtime.KeepAlivedo and when is it required? - Q359Why can removing elements from a slice of pointers leak memory?
- Q360Do Go maps shrink after you delete entries? How do you reclaim the memory?
- Q361How do goroutine leaks happen, and how do you detect them?
- Q362Is
time.Afterin a loop a memory leak? What changed in Go 1.23?