零长度和零上限切片是否仍然指向底层数组并防止垃圾回收?

时间:2014-05-21 15:48:29

标签: garbage-collection go

让我们采取以下方案:

a := make([]int, 10000)
a = a[len(a):]

正如我们从" Go Slices: Usage and Internals"有可能的问题"在缩小范围。对于任何切片a,如果你a[start:end]仍然指向原始内存,那么如果你不复制,一个小的下行可能会长时间在内存中保留一个非常大的数组

但是,选择这种情况会导致切片不仅应具有零长度,而且应具有零容量。可以针对构造a = a[0:0:0]询问类似的问题。

当前的实现是否仍然维护指向底层内存的指针,防止它被垃圾回收,或者它是否识别出没有lencap的切片无法引用任何内容,以及因此垃圾收集原始后备阵列在下一次GC暂停期间(假设不存在其他参考)?

编辑:在Playground上使用reflectunsafe进行操作会发现指针不为零:

func main() {
    a := make([]int, 10000)
    a = a[len(a):]

    aHeader := *(*reflect.SliceHeader)((unsafe.Pointer(&a)))
    fmt.Println(aHeader.Data)

    a = make([]int, 0, 0)
    aHeader = *(*reflect.SliceHeader)((unsafe.Pointer(&a)))
    fmt.Println(aHeader.Data)
}

http://play.golang.org/p/L0tuzN4ULn

然而,这并不一定能回答这个问题,因为从来没有任何东西的第二个切片也有一个非零指针作为数据字段。即便如此,指针可能只是uintptr(&a[len(a)-1]) + sizeof(int),它将位于支持内存块之外,因此不会触发实际的垃圾收集,尽管这似乎不太可能,因为这样可以防止其他事情的垃圾收集。非零值也可能只是游乐场的怪异。

1 个答案:

答案 0 :(得分:1)

如您的示例所示,重新切片会复制切片标头,包括指向新切片的数据指针,因此我将一个小测试放在一起,尝试强制运行时尽可能重用内存。

我希望这更具确定性,但至少在x86_64上使用go1.3,它表明原始数组使用的内存最终会被重用(它在这种形式的游乐场中不起作用)

package main

import (
    "fmt"
    "unsafe"
)

func check(i uintptr) {
    fmt.Printf("Value at %d: %d\n", i, *(*int64)(unsafe.Pointer(i)))
}

func garbage() string {
    s := ""
    for i := 0; i < 100000; i++ {
        s += "x"
    }
    return s
}

func main() {
    s := make([]int64, 100000)
    s[0] = 42

    p := uintptr(unsafe.Pointer(&s[0]))
    check(p)

    z := s[0:0:0]
    s = nil
    fmt.Println(z)
    garbage()
    check(p)
}