go 垃圾会收集部分切片吗? [英] Does go garbage collect parts of slices?

查看:19
本文介绍了go 垃圾会收集部分切片吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

如果我实现这样的队列...

主包进口(fmt")func PopFront(q *[]string) 字符串 {r := (*q)[0]*q = (*q)[1:len(*q)]返回 r}func PushBack(q *[]string, 一个字符串) {*q = 追加(*q, a)}功能主要(){q := make([]string, 0)PushBack(&q, "A")fmt.Println(q)PushBack(&q, "B")fmt.Println(q)PushBack(&q, "C")fmt.Println(q)流行前线(&q)fmt.Println(q)流行前线(&q)fmt.Println(q)}

...我最终得到一个数组 ["A", "B", "C"] 没有指向前两个元素的切片.由于切片的开始"指针永远不能递减(AFAIK),因此永远不能访问这些元素.

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

解决方案

切片只是描述符(类似结构的小数据结构),如果不被引用,将被正确地回收.

另一方面,切片(描述符指向的)的底层数组在通过重新切片创建的所有切片之间共享:引用 Go 语言规范:切片类型:

<块引用>

切片一旦被初始化,总是与保存其元素的底层数组相关联.因此,一个切片与其数组以及同一数组的其他切片共享存储空间;相比之下,不同的数组总是代表不同的存储.

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

官方声明:

博文 Go Slices:用法和内部 Andrew Gerrand 明确说明了这种行为:

<块引用>

如前所述,重新切片切片不会复制底层数组.整个数组将保存在内存中,直到不再被引用.有时这会导致程序在只需要一小部分时将所有数据保存在内存中.

...

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

回到你的例子

虽然底层数组不会被释放,但请注意,如果您将新元素添加到队列中,内置的 append 函数有时可能会分配一个新数组并将当前元素复制到新的– 但是复制只会复制切片的元素,而不是整个底层数组!当这种重新分配和复制发生时,旧"如果不存在其他引用,则数组可能会被垃圾回收.

另外一个很重要的事情是,如果从前面弹出一个元素,切片将被重新切片并且不包含对弹出元素的引用,但是由于底层数组仍然包含该值,因此该值也将保留在内存(不仅仅是数组).建议无论何时从队列(切片/数组)中弹出或删除元素,始终将其归零(切片中的相应元素),这样该值就不会不必要地保留在内存中.如果您的切片包含指向大数据结构的指针,这将变得更加重要.

func PopFront(q *[]string) string {r := (*q)[0](*q)[0] = "//总是将移除的元素归零!*q = (*q)[1:len(*q)]返回 r}

提到了 Slice Tricks wiki 页面:

<块引用>

删除而不保留​​顺序

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

注意 如果元素的类型是pointer或带有指针字段的struct,需要进行垃圾回收,Cut<的上述实现/code> 和 Delete 有一个潜在的内存泄漏 问题:一些具有值的元素仍然被切片 a 引用,因此无法收集.

If I implement a queue like this...

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

... I end up with an array ["A", "B", "C"] that has no slices pointing to the first two elements. Since the "start" pointer of a slice can never be decremented (AFAIK), those elements can never be accessed.

Is Go's garbage collector smart enough to free them?

解决方案

Slices are just descriptors (small struct-like data structures) which if not referenced will be garbage collected properly.

The underlying array for a slice (to which the descriptor points to) on the other hand is shared between all slices that are created by reslicing it: quoting from the Go Language Specification: Slice Types:

A slice, once initialized, is always associated with an underlying array that holds its elements. A slice therefore shares storage with its array and with other slices of the same array; by contrast, distinct arrays always represent distinct storage.

Therefore if at least one slice exists, or a variable holding the array (if a slice was created by slicing the array), it will not be garbage collected.

Official Statement about this:

The blog post Go Slices: usage and internals By Andrew Gerrand clearly states this behaviour:

As mentioned earlier, re-slicing a slice doesn't make a copy of the underlying array. The full array will be kept in memory until it is no longer referenced. Occasionally this can cause the program to hold all the data in memory when only a small piece of it is needed.

...

Since the slice references the original array, as long as the slice is kept around the garbage collector can't release the array.

Back to your example

While the underlying array will not be freed, note that if you add new elements to the queue, the built-in append function occasionally might allocate a new array and copy the current elements to the new – but copying will only copy the elements of the slice and not the whole underlying array! When such a reallocation and copying occurs, the "old" array may be garbage collected if no other reference exists to it.

Also another very important thing is that if an element is popped from the front, the slice will be resliced and not contain a reference to the popped element, but since the underlying array still contains that value, the value will also remain in memory (not just the array). It is recommended that whenever an element is popped or removed from your queue (slice/array), always zero it (its respective element in the slice) so the value will not remain in memory needlessly. This becomes even more critical if your slice contains pointers to big data structures.

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

This is mentioned Slice Tricks wiki page:

Delete without preserving order

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

NOTE If the type of the element is a pointer or a struct with pointer fields, which need to be garbage collected, the above implementations of Cut and Delete have a potential memory leak problem: some elements with values are still referenced by slice a and thus can not be collected.

这篇关于go 垃圾会收集部分切片吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆