Why can removing elements from a slice of pointers leak memory?
Question 359MediumGo 1.22 to 1.25
Reslicing only changes len. The elements past the new length stay in the backing array and still reference their objects, so the GC scans the whole array and keeps those objects alive.
type Conn struct{ buf [64 << 10]byte }
// Leaky pop: the last *Conn is still referenced by the backing array
func popLeaky(s []*Conn) ([]*Conn, *Conn) {
c := s[len(s)-1]
return s[:len(s)-1], c
}
// Correct: clear the vacated slot
func pop(s []*Conn) ([]*Conn, *Conn) {
c := s[len(s)-1]
s[len(s)-1] = nil
return s[:len(s)-1], c
}
func removeFirst(s []*Conn) []*Conn {
return slices.Delete(s, 0, 1) // Go 1.22+: zeroes the obsolete tail elements
}
Since Go 1.22, slices.Delete, DeleteFunc, Compact, CompactFunc and Replace zero the elements between the new and old length. Code that relied on the old tail contents surviving after Delete changed behaviour with that release. For queues implemented as q = q[1:], the front of the array is never reused or freed while the slice is alive. Clear q[0] before advancing, and periodically copy the queue into a fresh slice or use a ring buffer.
The same thing happens with clear(s) compared to s = s[:0]: only clear releases the referenced objects.
More on Memory, GC & Runtime Internals
- Q357What does
runtime.KeepAlivedo and when is it required? - Q358What does this print, and what memory problem does it illustrate?
- 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? - Q363How do you find a memory leak in production with pprof?