In Go, deleting an entry of a map of pointers causes memory leak?

前端 未结 2 951
悲&欢浪女
悲&欢浪女 2021-01-14 16:21

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

2条回答
  •  说谎
    说谎 (楼主)
    2021-01-14 16:58

    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.

提交回复
热议问题