在Go语言中,删除指针映射的条目会导致内存泄漏吗?

huangapple go评论83阅读模式
英文:

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

问题

首次使用,SliceTricks 中的第一个注意事项指出,在切片或删除指针切片中的元素时可能存在潜在的内存泄漏问题。

对于 map,是否也是如此呢?例如:https://play.golang.org/p/67cN0JggWY

在从 map 中删除条目之前,我们是否应该将其设置为 nil?像这样:

m["foo"] = nil

如果我们只是清空 map 呢?

m = make(map[string]*myStruct)

垃圾回收器会自动回收吗?

提前感谢。

英文:

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

答案1

得分: 8

检查源代码

虽然没有在任何地方记录,但是通过检查源代码:runtime/hashmap.gomapdelete() 函数:

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	}

如你所见,键(第 600 行)和值(第 602 行)的存储空间被清空/置零。

这意味着如果键或值中的任何一个是指针,或者它们是包含指针的复杂类型的值,它们将被清零,因此指向的对象不再被映射的内部数据结构引用,所以这里没有内存泄漏。

当没有对完整的 map 值的引用时,map 的完整内存区域将被垃圾回收,键和值中包含的所有指针也不再被映射持有;如果没有其他地方引用指向的对象,它们将被适当地垃圾回收。

构建一个示例来证明这一点

我们还可以构建一个测试代码来证明这一点,而无需检查源代码:

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)
}

输出结果(在 Go Playground 上尝试):

Put in map: 0x1040a128 &{X:1 Y:2}
Finalized: 0x1040a128 &{X:1 Y:2}
map[]

这段代码做了什么?它创建了一个 *Point 值(指向结构体的指针),将其放入映射中,并注册一个函数,当该指针变得不可达时应该被调用(使用 runtime.SetFinalizer()),然后删除包含该指针的条目。然后我们调用 runtime.GC() 来“强制”立即进行垃圾回收。最后,我打印了映射,只是为了确保整个映射没有因为某种优化而被垃圾回收。

结果如何?我们看到注册的函数被调用,这证明了指针在 delete() 调用的结果中从映射中被移除,因为(由于我们没有其他引用),它符合垃圾回收的条件。

英文:

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.

答案2

得分: 5

不,从映射中删除时不会出现任何内存泄漏。

对于切片而言,由于切片实际上使用了底层数组,只要切片存在,即使它只使用了数组中的一个槽位,数组内部的指针项也不会被垃圾回收。

切片描述了数组的一部分”,这意味着数组需要存在才能存在切片,并且只要有代码指向切片,数组就不会被垃圾回收。

英文:

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.

huangapple
  • 本文由 发表于 2016年9月8日 21:36:15
  • 转载请务必保留本文链接:https://go.coder-hub.com/39392467.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定