从Golang Slice的内存泄漏来理解Slice的使用逻辑

Golang虽然是自带GC的语言,仍然存在内存泄漏的状况,这片文章总结了Golang中内存泄漏的状况git

其中Slice的内存泄漏是最容易中招的,看看这个PR: writev 的 leak,Golang官方都踩了坑。github

本文将就其中的Slice内存泄漏的状况作分析,并介绍Slice实现和使用的一些关键逻辑。golang

Slice如何内存泄漏

Golang是自带GC的,若是资源一直被占用,是不会被自动释放的,好比下面的代码,若是传入的slice b是很大的,而后引用很小部分给全局量a,那么b未被引用的部分就不会被释放,形成了所谓的内存泄漏。bash

var a []int

func test(b []int) {
	a = b[:1]
	return
}
复制代码

想要理解这个内存泄漏,主要就是理解上面的a = b[:1]是一个引用,其实新、旧slice指向的都是同一片内存地址,那么只要全局量a在,b就不会被回收。app

Slice的使用逻辑

关于新、旧slice指向同一片地址空间,具体能够看下面的代码和说明图,关键点在于ui

  • b:=a[1:3]时,ba指向了同一片地址上的sliceb看到的是索引为1和2的两个成员,因此长度为2, 2也指定了b的读写长度。
  • 经过修改b[0]的值为11a[1]的值也会随之改变,验证了他们指向同一个地址空间
  • b的容量为9,表明了b引用slice的真实长度
  • 能够经过b=a[1:3:2],将bcap限制为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]地址是一致的:spa

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
}

复制代码
相关文章
相关标签/搜索