golang slice中的内存泄漏

时间:2019-03-07 13:50:56

标签: pointers go copy append slice

我刚刚开始学习go,在经历切片技巧时,有两点非常令人困惑。谁能帮我澄清一下。

要在给定的切片中剪切元素

方法1:

a = append(a[:i], a[j:]...)

但是要注意的是,如果使用指针并且推荐的方法是使用,可能会导致内存泄漏

方法2:

copy(a[i:], a[j:])
for k, n := len(a)-j+i, len(a); k < n; k++ {
    a[k] = nil // or the zero value of T
}
a = a[:len(a)-j+i]

任何人都可以帮助我了解内存泄漏如何发生。 我知道子切片将由主数组支持。我的想法与指针无关,我们是否必须始终遵循方法2。

在@icza和@Volker回答后更新。

让我们说您有一个结构

type Books struct {
    title   string
    author  string
}

var Book1 Books
var Book2 Books 

    /* book 1 specification */
    Book1.title = "Go Programming"
    Book1.author = "Mahesh Kumar"

    Book2.title = "Go Programming"
    Book2.author = "Mahesh Kumar"

    var bkSlice = []Books{Book1, Book2}
    var bkprtSlice = []*Books{&Book1, &Book2}

现在正在

bkSlice = bkSlice[:1]

bkSlice仍将Book2保留在后备阵列中,后者仍在内存中,并且不需要。 所以我们需要做

bkSlice[1] = Books{}

,以便将其GC化。我知道指针必须为零,因为切片将保留对数组背后对象的不必要引用。

1 个答案:

答案 0 :(得分:3)

最简单的可以通过简单的切片表达式来证明。

让我们从一片*int指针开始:

s := []*int{new(int), new(int)}

此切片具有一个长度为2的支持数组,并且包含2个非nil指针,指向已分配的整数(在支持数组之外)。

现在,如果我们重新切片该切片:

s = s[:1]

长度将变为1。后备数组(保存2个指针)没有被触摸,它仍然保存2个有效的指针。即使我们现在不使用第二个指针,因为它在内存中(它是后备数组),所以指针对象(它是用于存储int值的内存空间)无法被垃圾释放收藏家。

如果您从中间“剪切”多个元素,也会发生同样的事情。如果原始切片(及其支持的数组)中填充了非{nil指针,并且您没有将它们归零(使用nil),则它们将保留在内存中。

非指针为什么不成问题?

实际上,这是所有指针和“标头”类型(例如切片和字符串)(不仅仅是指针)的问题。

如果切片类型为[]int而不是[]*int,则切片将仅“隐藏” int类型的元素,这些元素必须作为一部分保留在内存中支持数组,无论是否有包含它的切片。 元素不是对存储在数组外部的对象的引用,而指针是指在数组外部的对象。

如果切片包含指针,并且您在切片操作之前nil,如果没有其他对指向对象的引用(如果数组是唯一保存指针的对象),则可以释放它们,由于仍然有切片(因此还有后备阵列),因此不会保留。

更新

当您拥有一片结构时:

var bkSlice = []Books{Book1, Book2}

如果您像这样切片:

bkSlice = bkSlice[:1]

Book2将通过bkSlice变得不可访问,但仍将保留在内存中(作为后备阵列的一部分)。

您不能nil,因为nil对结构不是有效值。不过,您可以像这样将其zero value分配给它:

bkSlice[1] = Book{}
bkSlice = bkSlice[:1]

请注意,Books结构值仍将作为后备数组的第二个元素在内存中,但该结构将为零值,因此将不包含字符串引用,因此是原著并且标题字符串可以被垃圾回收(如果没有其他人引用它们;更确切地说是从字符串标题引用的字节片)。

一般规则是“递归”:您只需将引用位于后备数组外部的内存的元素归零即可。因此,如果您有一片仅包含例如int字段,您无需将其归零,实际上,这只是不必要的额外工作。如果struct的字段是指针,切片或例如其他具有指针或分片等的结构类型,则应将其清零,以便删除对后备数组之外的内存的引用。