從Golang Slice的內存洩漏來理解Slice的使用邏輯

2022-01-07 Go語言中文網

Golang雖然是自帶GC的語言,仍然存在內存洩漏的情況,這篇文章總結了Golang中內存洩漏的情況。

其中Slice的內存洩漏是最容易中招的,看看這個PR:https://github.com/golang/go/pull/32138,Golang官方都踩了坑。

本文將就其中的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指向同一片地址空間,具體可以看下面的代碼和說明圖,關鍵點在於

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    fmt.Println(a[1])   fmt.Println(len(a), cap(a))    fmt.Println(len(b), cap(b))   }

如果想避免這個問題,文章頂部的連結裡給出了方法, 它之所以能夠重新分配的原因在於append方法的實現,如果append的目標slice空間不夠,會重新申請一個array來放需要append的內容,所以&b[0]和&a[0]的值是不一樣的,而&a[0]和&c[0]地址是一致的:

var b []intvar c []intfunc test(a []int) {  c = a[:1]  b = append(a[:0:0], a[:1]...)    fmt.Println(&a[0], &c[0], &b[0]) }


推薦閱讀

喜歡本文的朋友,歡迎關注「Go語言中文網」:

Go語言中文網啟用微信學習交流群,歡迎加微信:274768166,投稿亦歡迎

相關焦點

  • 徹底理解Golang Slice
    golang 有三個常用的高級類型slice、map、channel, 它們都是引用類型,當引用類型作為函數參數時,可能會修改原內容數據。> t.Log(len(slice1), cap(slice1))  // 1, 1, 24 // 初始化方式2:使用字面量 slice2 := []int{1, 2, 3, 4} t.Log(len(slice2), cap(slice2))  // 4, 4, 24 // 初始化方式3:使用make創建slice slice3
  • Golang【源碼系列】深入了解slice
    首先是,array是一個unsafe.Pointer類型,其指向的對象可以被gc掃描到,因此slice的內存不需要自己管理;另外,除了常規的slice,源碼中還有一個notInHeapSlice,即不在堆上分配內存的slice,其通常分配在系統棧空間上存放一些內存管理的元數據,在sysAlloc()等內部函數中使用,詳情可以參考runtime/malloc.go。
  • Golang slice 使用技巧
    在 Go 語言項目中大量的使用 slice, 我總結三年來對 slice 的一些操作技巧,以方便可以高效的使用 slice, 並使用 slice 解決一些棘手的問題。slice 的基本操作先熟悉一些 slice 的基本的操作, 對最常規的 : 操作就可玩出很多花樣。
  • 獲得了「官方自己都會踩的」坑認證:slice 類型內存洩露的邏輯
    我們來看看子切片截取為什麼會導致內存洩露。通過減少堆上創建的對象來降低標記的壓力,一方面可以節省 GC 整體使用的 CPU,最終也就大幅縮減了用戶服務的延遲,那些說不用優化的笑笑就好。在優化 GC 時,最直接的思路就是對創建對象進行復用,官方提供了 sync.Pool 來幫助用戶對他們的應用對象進行復用。這裡看 Go 的基本類型:map 和 slice。
  • 一種理解 Golang Slice 的模型
    【導讀】本文以圖示的方式給出一種理解 slice 的模型的方法,分析了在特殊場景的slice用法。概述Golang 中 slice 極似其他語言中數組,但又有諸多不同,因此容易使初學者產生一些誤解,並在使用時不易察覺地掉進各種坑中。
  • 關於Golang切片Slice和append的有趣問題
    切片{1,2},所以y和x指向的內存地址是一樣的;【2】因為y指向的內存地址和x是一樣的,在尾部append一個值的時候,會擠掉後面的值3,故這時候x和y都為1,2,10【3】這時候y又再次appned,超出了原來的大小3,這時候會會分配一個更大數組來容納,會新建一塊獨立的內存地址給到y(y獨立了,和x沒有什麼關係了)。
  • Golang 筆記(三):一種理解 Slice 的模型
    本篇小文,首先從 Go 語言官方博客出發,鋪陳官方給出的 slice 的相關語法;其次以圖示的方式給出一種理解 slice 的模型;最後再總結分析一些特殊的使用情況,以期在多個角度對 slice 都有個更清晰側寫。如不願看繁瑣敘述過程,可直接跳到最後小結看總結。
  • 「語言學習」Go語言之slice特性
    Go語言學習slice介紹和說明golang的數據結構也很多,如List,array,map等,但是有個很特別的數據結構是slice,也叫切片那麼什麼是slice呢?其實slice也算是golang語言特有的數據結構,底層是以數組作為支撐;啥概念呢,就是說在申請一塊內存進行數組的存放的時候,slice就像數組對外開放的一扇窗口,讓你看到想給你看到的內容。
  • golang踩坑 1.slice傳參和for range賦值
    前言 這篇文章我們來聊聊slice當作參數傳遞的時候會出現什麼問題。還有for range在遍歷賦值的時候會出現什麼問題。2. slice傳參 package mainimport "fmt"func main() { slice := []int{1, 2} fmt.Printf("data:%v, len:%d, cap:%d\n", slice, len(slice), cap(slice)) updateslice
  • 聽說你還搞不懂Golang的Slice?看這一篇就夠了!
    (slice)來這麼做。使用 make 創建在創建之前我們先簡單地了解一下 make 函數的作用。make 只能應用於三種數據類型:本文中的 slice、以及後面要說的 map 和 chan。make 會為它們分配內存、初始化一個對應類型的對象並返回。注意,返回值的類型依然是被 make 的那個類型,make 只對其做了一個引用。
  • 「Golang」for range 使用方法及避坑指南
    接下來,通過幾個代碼來展示出,for循環,如何實現while,do..while 的相關邏輯。來看看語法糖在我們的Go編寫的業務邏輯中,常用的循環方式,為經典的三段式循環,即for i := 0; i < N; i++ {},這種循環可以幫我們方便的遍歷數組,切片等數據結構,還可以輕鬆的進行一定次數循環的操作,那麼當我們想要遍歷map和channel時,該如何呢?
  • c++ list slice專題及常見問題 - CSDN
    切片在指針的基礎上增加了大小,以約束切片對應的內存區域。切片使用中無法對切片內部的地址和大小進行手動調整,因此切片比指針更加安全。獲取原有切片生成切片時當開始位置和結束位置都被忽略時表示和原有切片一樣,這樣生成的切片與原有切片在數據呢容上也是一致的。
  • 跟 Dave Cheney 大神重學 Go Slice:有新收穫
    切片 slices Go 的切片類型與數組類型有兩個不同的地方:切片其實沒有固定長度,一個切片的長度沒有被聲明為其類型的一部分,而是被保留在切片結構本身中並且可以通過內置函數 len 來重置他。Header 想要理解 slice 是如何做到本身是一個類,並且又是一個指針的話,就得理解  slice 的底層結構[1]。
  • Golang最細節篇— struct{} 空結構體究竟是啥?
    小結:golang 使用 mallocgc 分配內存的時候,如果 size 為 0 的時候,統一返回的都是全局變量 zerobase 的地址。有這種全局唯一的特殊的地址也方便後面一些邏輯的特殊處理。golang 核心的幾個複合結構 map ,chan ,slice 都能結合 struct{}  使用。
  • Golang 性能測試 (1)
    func BenchmarkSort100(t *testing.B) { slice := ints[0:100] t.ResetTimer() for i := 0; i < t.N; i++ { sort.Ints(slice) }}// 長度為 1w 的數據使用上述代碼排序func BenchmarkQsort10k
  • Array.slice 8種不同用法
    用法1:簡單的複製const arr2 = arr.slice沒有任何參數的 slice執行一個簡單的淺拷貝。當前,主流的用法還是使用展開運算符合來實現,但是如果在舊的代碼庫中,或者沒有使用 babel的構建步驟,可能仍然希望使用 slice。
  • JavaScript 數組:對比 slice 與 splice
    在使用中,可以通過選擇一個具有強語義表達性的 API 來減少混淆的發生。數組的 slice (ECMAScript 5.1 標準 15.4.4.10 節)非常類似於字符串的 slice。根據規範,slice 需要兩個參數,起點和終點。它會返回一個包含了從起點開始,到終點之前之間所有元素的新數組。
  • Golang 語言的內存管理
    理論上,程式設計師可申請的堆大小為虛擬內存的大小,進程棧的大小 64bits 的 Windows 默認 1MB,64bits 的 Linux 默認 10MB;分配效率:棧由作業系統自動分配,會在硬體層級對棧提供支持:分配專門的寄存器存放棧的地址,壓棧出棧都有專門的指令執行,這就決定了棧的效率比較高。堆則是由庫函數或運算符來完成申請與管理,實現機制較為複雜,頻繁的內存申請容易產生內存碎片。
  • 【重學JS系列】slice用法大合集
    當前,主流的用法還是使用展開運算符合來實現,但是如果在舊的代碼庫中,或者沒有使用babel的構建步驟,可能仍然希望使用slice。用法2:獲取從 N 開始的子數組使用slice方法最簡單的方法就是原始數組從N開始抽取的所有元素。
  • 【譯】Rust中的array、vector和slice
    比起 C/C++的未定義行為,我寧願使用 Rust 的 panic。VectorArray 最大的一個限制是它的固定大小。閱讀 C 的庫函數realloc來理解這是如何運作的。SliceSlice 就像一個 array 或 vector 的臨時視圖(temporary views)。