1. 程式人生 > 實用技巧 >go 避免切片記憶體洩露

go 避免切片記憶體洩露

避免切片記憶體洩漏

如前面所說,切片操作並不會複製底層的資料。底層的陣列會被儲存在記憶體中,直到它不再被引用。但是有時候可能會因為一個小的記憶體引用而導致底層整個陣列處於被使用的狀態,這會延遲自動記憶體回收器對底層陣列的回收。

例如,FindPhoneNumber函式載入整個檔案到記憶體,然後搜尋第一個出現的電話號碼,最後結果以切片方式返回。

func FindPhoneNumber(filename string) []byte {
    b, _ := ioutil.ReadFile(filename)
    return regexp.MustCompile("[0-9]+").Find(b)
}

這段程式碼返回的[]byte指向儲存整個檔案的陣列。因為切片引用了整個原始陣列,導致自動垃圾回收器不能及時釋放底層陣列的空間。一個小的需求可能導致需要長時間儲存整個檔案資料。這雖然這並不是傳統意義上的記憶體洩漏,但是可能會拖慢系統的整體效能。

要修復這個問題,可以將感興趣的資料複製到一個新的切片中(資料的傳值是Go語言程式設計的一個哲學,雖然傳值有一定的代價,但是換取的好處是切斷了對原始資料的依賴):

func FindPhoneNumber(filename string) []byte {
    b, _ := ioutil.ReadFile(filename)
    b = regexp.MustCompile("[0-9]+").Find(b)
    return append([]byte{}, b...)
}

類似的問題,在刪除切片元素時可能會遇到。假設切片裡存放的是指標物件,那麼下面刪除末尾的元素後,被刪除的元素依然被切片底層陣列引用,從而導致不能及時被自動垃圾回收器回收(這要依賴回收器的實現方式):

var a []*int{ ... }
a = a[:len(a)-1]    // 被刪除的最後一個元素依然被引用, 可能導致GC操作被阻礙

保險的方式是先將需要自動記憶體回收的元素設定為nil,保證自動回收器可以發現需要回收的物件,然後再進行切片的刪除操作:

var a []*int{ ... }
a[len(a)-1] = nil // GC回收最後一個元素記憶體
a = a[:len(a)-1]  // 從切片刪除最後一個元素

當然,如果切片存在的週期很短的話,可以不用刻意處理這個問題。因為如果切片本身已經可以被GC回收的話,切片對應的每個元素自然也就是可以被回收的了。