这种类型的golang字符串切片是否会在底层字节数组中泄漏内存?

4

buf = buf[n:] 这样的 Golang 字符串切片操作会导致底层字节数组内存泄漏吗?

如果是,那么有没有可能获取有关底层字节数组的任何信息(例如容量或基础内存地址),以便验证泄漏情况?

请参考下面的示例代码:

var buf string

func push(s string) {
    buf += s
    if len(buf) > 3 {
        buf = buf[len(buf)-3:] // can this line leak memory in underlying byte array?
    }
    fmt.Printf("buf=[%v]\n", buf)
}

Run it on playground


2
字符串是不可变的,因此每次都会分配一个新的字符串。 - JimB
2
切片操作不会创建新的字符串,但是将切片赋值回缓冲区会创建一个新的字符串。 - Zan Lynx
1
我现在其实不太确定了。也许字符串的字节确实会残留下来... - Zan Lynx
1
@Everton:可以放心地假设并进行简单的测试。只需在循环中使用gctrace运行您的“push”即可。在我的系统上,它保持稳定的4MB堆。 - JimB
1
请参阅https://dev59.com/vHLYa4cB1Zd3GeqPc-Q6。 - Zan Lynx
显示剩余3条评论
2个回答

5

不,这个例子不会导致内存泄漏,因为每次调用push时都需要分配新的字符串。有可能有一些字节会被保留以减少分配次数,但该过程是一个实现细节,不应考虑。

如果您想到了一个类似的情况,即在切片操作的结果上赋值,但从未使用append。没有泄漏,只要您理解切片的语义即可。

s := make([]byte, 1024)
s = s[1000:]
fmt.Println(s, len(s), cap(s))

这个例子会保留前1000个字节的空间,但是无法访问。解决方法很简单,就是不要这么做。避免这种情况并不难,如果你确实需要释放底层数组,请使用 copy 将字节移动到一个新的切片中。

对于字符串也是同样的道理:

s = s[1020:]
// may leave the first 1000 bytes allocated

很容易明白并避免出现这种情况。如果您正在使用大量字符串,最好使用 []byte, 这样可以更好地控制分配,并在需要时复制字节。


谢谢你的分享:"可以安全地假设并且易于测试。只需在循环中使用gctrace运行您的推送即可。在我的系统上,它保持稳定的4MB堆。- JimB" - Everton
但是,我想知道是否持有对s[i:j]的引用会使s保留在内存中?例如,在Java中,s.substring(i,j)一直持有s的数据,直到Java 8。 - kostya
2
@kostya:是的,一个切片可以像预期的那样保持字节的存活状态,http://play.golang.org/p/H6zQ0fMi-g,但这个示例并不是在做这个。不过我会加上一条注释。 - JimB
@JimB,这是一个非常有趣的问题。我会尝试写下一个替代答案。 - kostya

3
将字符串应用切片表达式p := s[i:j]的结果是一个字符串。据我所知,Go语言规范(https://golang.org/ref/spec)没有指定p将由与s相同的内存支持。
然而,在Go 1.6及更早版本中,对p的实时引用会防止s被垃圾回收。这在将来的Go版本中可能会发生变化。
有趣的是,在Java 8之前,String.substring方法的实现方式与此相同。但是,在Java 8中,substring返回一个副本。
回到您的示例。每次调用push函数时,以下行实际上都会创建一个新的字符串实例:
buf += s

旧的 buf 实例会被垃圾回收。因此,你的示例不受上述问题的影响。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接