Golang Slice中的内存泄漏

Sam*_*dra 4 pointers copy append go slice

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

在给定的切片中切割元素

方法1:

a = append(a[:i], a[j:]...)
Run Code Online (Sandbox Code Playgroud)

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

方法二:

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]
Run Code Online (Sandbox Code Playgroud)

谁能帮助我了解内存泄漏是如何发生的。我知道子切片将由主数组支持。我的想法与指针无关,我们是否必须始终遵循方法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}
Run Code Online (Sandbox Code Playgroud)

现在正在做

bkSlice = bkSlice[:1]
Run Code Online (Sandbox Code Playgroud)

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

bkSlice[1] = Books{}
Run Code Online (Sandbox Code Playgroud)

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

icz*_*cza 6

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

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

s := []*int{new(int), new(int)}
Run Code Online (Sandbox Code Playgroud)

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

现在,如果我们对这个切片进行切片:

s = s[:1]
Run Code Online (Sandbox Code Playgroud)

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

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

为什么非指针不存在此问题?

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

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

如果切片包含指针,nil而切片操作之前是指针,则如果没有其他对指向对象的引用(如果数组是唯一保存指针的对象),则可以释放它们,因为它们仍然具有切片(以及后备阵列)。

更新:

当您拥有一部分结构时:

var bkSlice = []Books{Book1, Book2}
Run Code Online (Sandbox Code Playgroud)

如果像这样切片:

bkSlice = bkSlice[:1]
Run Code Online (Sandbox Code Playgroud)

Book2将通过变为unreachabe bkSlice,但仍将在内存中(作为后备阵列的一部分)。

您不能这样nil做,因为nil它不是结构的有效值。但是,您可以像这样为它分配零值

bkSlice[1] = Book{}
bkSlice = bkSlice[:1]
Run Code Online (Sandbox Code Playgroud)

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

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