小编典典

去垃圾会收集切片的一部分吗?

go

如果我实现这样的队列…

package main

import(
    "fmt"
)

func PopFront(q *[]string) string {
    r := (*q)[0]
    *q = (*q)[1:len(*q)]
    return r
}

func PushBack(q *[]string, a string) {
    *q = append(*q, a)
}

func main() {
    q := make([]string, 0)

    PushBack(&q, "A")
    fmt.Println(q)
    PushBack(&q, "B")
    fmt.Println(q)
    PushBack(&q, "C")
    fmt.Println(q)

    PopFront(&q)
    fmt.Println(q)
    PopFront(&q)
    fmt.Println(q)      
}

…我得到的数组["A", "B", "C"]没有指向前两个元素的切片。由于切片的“开始”指针永远不会递减(AFAIK),因此永远无法访问这些元素。

Go的垃圾收集器足够聪明以释放它们吗?


阅读 218

收藏
2020-07-02

共1个答案

小编典典

切片只是描述符(类似于小型结构的数据结构),如果不对其进行引用,则会对其进行正确的垃圾回收。

另一方面,切片的基本数组(描述符指向该数组)在所有切片之间 共享 ,这些切片通过切片来 共享 :引用自Go语言规范:Slice
Types

切片一旦初始化,便始终与包含其元素的基础数组关联。因此,一个片与其阵列以及同一阵列的其他片共享存储。相反,不同的数组始终代表不同的存储。

因此,如果存在至少一个片,或保存数组的变量(如果通过对数组进行切片来创建片),则不会进行垃圾回收。

关于此的官方声明:

安德鲁·格朗德(Andrew Gerrand)撰写的博客文章 Go
Slices:用法和内部

原理明确说明了这种行为:

如前所述,对切片进行重新切片不会复制基础数组。 完整的数组将保留在内存中,直到不再被引用为止。
有时,这可能导致程序仅需要一小部分数据时就将所有数据保存在内存中。

由于切片引用了原始数组, 因此只要将切片保留在垃圾收集器周围,就无法释放该数组

回到你的例子

虽然基础数组不会被释放,但是请注意,如果您将新元素添加到队列中,则内置append函数有时可能会分配新数组并将当前元素复制到新元素上,但是复制只会复制切片的元素而不是整个基础数组!当发生这种重新分配和复制时,如果没有其他引用,则可能会“回收”旧数组。

另一个非常重要的事情是,如果从前面弹出一个元素,则切片将被切片并且不包含对弹出元素的引用,但是由于基础数组仍包含该值,因此该值也将保留在内存中(而不是只是数组)。建议每当从队列(切片/数组)中弹出或删除一个元素时,
始终将其 (切片中其相应的元素) 置零, 这样该值就不会不必要地保留在内存中。如果您的分片包含指向大数据结构的指针,则这一点变得尤为重要。

func PopFront(q *[]string) string {
    r := (*q)[0]
    (*q)[0] = ""  // Always zero the removed element!
    *q = (*q)[1:len(*q)]
    return r
}

这里提到了Slice Tricks
Wiki页面:

删除但不保留订单

a[i] = a[len(a)-1]
a = a[:len(a)-1]

注意 如果元素的类型是一个 指针 或指针字段,其需要被垃圾收集一个结构,上述实施方式CutDelete有潜在的 存储器泄露
的问题:其值的一些元素仍然由切片引用a并因此不能集。

2020-07-02