Golang虽然是自带GC的语言,仍然存在内存泄漏的情况,这片文章总结了Golang中内存泄漏的情况。
其中Slice的内存泄漏是最容易中招的,看看这个PR: writev 的 leak,Golang官方都踩了坑。
本文将就其中的Slice内存泄漏的情况做分析,并介绍Slice实现和使用的一些关键逻辑。
Slice如何内存泄漏
Golang是自带GC的,如果资源一直被占用,是不会被自动释放的,比如下面的代码,如果传入的slice b
是很大的,然后引用很小部分给全局量a
,那么b
未被引用的部分就不会被释放,造成了所谓的内存泄漏。
var a []int
func test(b []int) {
a = b[:1]
return
}
复制代码
想要理解这个内存泄漏,主要就是理解上面的a = b[:1]
是一个引用,其实新、旧slice
指向的都是同一片内存地址,那么只要全局量a
在,b
就不会被回收。
Slice的使用逻辑
关于新、旧slice
指向同一片地址空间,具体可以看下面的代码和说明图,关键点在于
b:=a[1:3]
时,b
和a
指向了同一片地址上的slice
,b
看到的是索引为1和2的两个成员,所以长度为2, 2也指定了b的读写长度。- 通过修改
b[0]
的值为11
,a[1]
的值也会随之改变,验证了他们指向同一个地址空间 b
的容量为9,代表了b
引用slice
的真实长度- 可以通过
b=a[1:3:2]
,将b
的cap
限制为2
func main() {
a := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
b := a[1:3]
b[0] = 11 // b[0]的改写,即对a[1]的改写
fmt.Println(a[1]) // a[1]被写成了11
fmt.Println(len(a), cap(a)) // 10 10
fmt.Println(len(b), cap(b)) // 2 9
}
复制代码
如何避免问题
如果想避免这个问题,文章顶部的链接里给出了方法, 它之所以能够重新分配的原因在于append
方法的实现,如果append的目标slice空间不够,会重新申请一个array
来放需要append
的内容,所以&b[0]
和&a[0]
的值是不一样的,而&a[0]
和&c[0]
地址是一致的:
var b []int
var c []int
// 现在,如果再没有其它值引用着承载着a元素的内存块,
// 则此内存块可以被回收了。
func test(a []int) {
c = a[:1]
b = append(a[:0:0], a[:1]...)
fmt.Println(&a[0], &c[0], &b[0]) //0xc0000aa030 0xc0000aa030 0xc0000b2038
}
复制代码
有疑问加站长微信联系(非本文作者)