In Go, deleting an entry of a map of pointers caus

2019-03-01 11:59发布

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

2条回答
冷血范
2楼-- · 2019-03-01 12:49

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.

查看更多
虎瘦雄心在
3楼-- · 2019-03-01 12:56

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.

查看更多
登录 后发表回答