first timer here,
The first NOTE in SliceTricks suggests that there is a potential memory leak problem when cutting or deleting elements in a slice of pointers.
Is the same true for a map? For example: https://play.golang.org/p/67cN0JggWY
Should we nil the entry before deleting from map? Like so:
m["foo"] = nil
What if we simply clear the map?
m = make(map[string]*myStruct)
Will the garbage collector still pick it up?
Thanks in advance
Checking the sources
Although this is not documented anywhere, checking the sources: runtime/hashmap.go
, mapdelete()
function:
558 func mapdelete(t *maptype, h *hmap, key unsafe.Pointer) {
// ...
600 memclr(k, uintptr(t.keysize))
601 v := unsafe.Pointer(uintptr(unsafe.Pointer(b)) + dataOffset + bucketCnt*uintptr(t.keysize) + i*uintptr(t.valuesize))
602 memclr(v, uintptr(t.valuesize))
// ...
618 }
As you can see, storage for both the key (line #600) and the value (line #602) are cleared / zeroed.
This means if any of the key or value was a pointer, or if they were values of complex types containing pointers, they are zeroed and therefore the pointed objects are no longer referenced by the internal data structures of the map, so there is no memory leak here.
When there is no more reference to a complete map
value, then the complete memory area of the map
will be garbage collected, and all the pointers included in keys and values are also not held anymore by the map; and if no one else has reference to the pointed objects, they will be garbage collected properly.
Constructing an example to prove this
We can also construct a test code which proves this without examining the sources:
type point struct {
X, Y int
}
var m = map[int]*point{}
func main() {
fillMap()
delete(m, 1)
runtime.GC()
time.Sleep(time.Second)
fmt.Println(m)
}
func fillMap() {
p := &point{1, 2}
runtime.SetFinalizer(p, func(p *point) {
fmt.Printf("Finalized: %p %+v\n", p, p)
})
m[1] = p
fmt.Printf("Put in map: %p %+v\n", p, p)
}
Output (try it on the Go Playground):
Put in map: 0x1040a128 &{X:1 Y:2}
Finalized: 0x1040a128 &{X:1 Y:2}
map[]
What does this do? It creates a *Point
value (pointer to a struct), puts it in the map, and registers a function that should be called when this pointer becomes unreachable (using runtime.SetFinalizer()
), and then deletes the entry containing this pointer. Then we call runtime.GC()
to "force" an immediate garbage collection. I also print the map at the end just to make sure the whole map is not garbage collected due to some optimization.
The result? We see the registered function gets called, which proves the pointer was removed from the map as the result of the delete()
call, because (since we had no other references to it) it was eligible for garbage collection.
No, there will not be any memory leaks when deleting from a map.
In case of slices, since a slice actually uses an underlying array, as long as the slice exists - even if it uses just one slot in that array - the pointer items inside the array can not get garbage collected.
"A slice describes a piece of an array" which implies the array needs to be there for the slice to exist and can not get collected by GC; as long as some code is pointing at the slice.