Go

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

All 38 Memory, GC & Runtime Internals questions