【问题标题】:In Go, deleting an entry of a map of pointers causes memory leak?在 Go 中,删除指针映射的条目会导致内存泄漏?
【发布时间】:2017-01-16 11:26:30
【问题描述】:

这里是第一个计时器,

SliceTricks 中的第一个 NOTE 表明在剪切或删除指针切片中的元素时存在潜在的内存泄漏问题。

地图也一样吗?例如:https://play.golang.org/p/67cN0JggWY

我们应该在从地图中删除之前将条目设为 nil 吗?像这样:

m["foo"] = nil

如果我们只是清除地图会怎样?

m = make(map[string]*myStruct)

垃圾收集器还会捡起来吗?

提前致谢

【问题讨论】:

  • 没有泄漏。我们不应该将条目归零。

标签: dictionary pointers go memory-leaks


【解决方案1】:

检查来源

虽然这在任何地方都没有记录,但请检查来源: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() 调用的结果是,因为(因为我们没有其他对它的引用)它有资格进行垃圾回收。

【讨论】:

  • 从您发布的代码的 sn-p 中不清楚是什么阻止了具有其他地方引用的值指针被 GC'd,例如如果您在fillMap() 调用之后的行中添加v := m[1],然后在删除映射条目后添加fmt.Println(v),则结构会按预期打印并且不会清零。为什么这很有用是有道理的,但从 mapdelete 代码 sn-p 中不清楚为什么它没有归零。
  • @ashgromnies 我不确定我明白你想说什么。如果有任何对可访问对象的引用,则不能 gc'd。如果地图有 only 引用并从地图中删除,则它有资格进行 gc。
【解决方案2】:

不,从地图中删除时不会有任何内存泄漏。

在切片的情况下,由于切片实际上使用底层数组,只要切片存在 - 即使它只使用该数组中的一个插槽 - 数组内的指针项也不会被垃圾回收。

"A slice describes a piece of an array" 这意味着数组需要存在切片才能存在并且不能被 GC 收集;只要一些代码指向切片。

【讨论】:

    猜你喜欢
    • 2021-06-14
    • 1970-01-01
    • 2016-06-11
    • 1970-01-01
    • 1970-01-01
    • 2015-11-29
    • 1970-01-01
    • 2021-02-26
    • 2011-01-07
    相关资源
    最近更新 更多