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

複製代碼
相關文章
相關標籤/搜索