[譯]Go:記憶體管理與記憶體分配
這篇文章是基於Go 1.13的。
當記憶體不再被使用時,標準庫就會自動執行Go記憶體管理,即從記憶體分配到Go自己的集合中(from allocation of the memory to its collection)。 雖然開發人員不用去和這些打交道,但是Go的記憶體管理做了很多優化以及有很多有趣的概念,所以也值得我們去探討與學習。
堆上的分配 Allocation on the heap
記憶體管理是在高併發環境以及集成了垃圾回收功能上所設計的。我們來演示一些簡單的例子:
package main
type smallStruct struct {
a,b int64
c,d float64
}
func main() {
smallAllocation()
}
//go:noinline
func smallAllocation() *smallStruct {
return &smallStruct{}
}
複製程式碼
註解go:noinline會阻止編譯器進行程式碼優化,否則編譯器會將這個函式去除因此就不能觸發記憶體分配。
執行下面命令可以確認Go進行了記憶體分配
go tool compile "-m" main.go
main.go:14:9: &smallStruct literal escapes to heap
複製程式碼
獲取該程式碼的彙編指令,多虧go tool compile -S main.go
,能夠精確地展示我們的記憶體分配情況。
0x001d 00029 (main.go:14) LEAQ type."".smallStruct(SB),AX
0x0024 00036 (main.go:14) PCDATA $0,$0
0x0024 00036 (main.go:14) MOVQ AX,(SP)
0x0028 00040 (main.go:14) CALL runtime.newobject(SB)
複製程式碼
函式newobject
是一個內建函式,用於新分配記憶體以及代理mallocgc
mallocgc
是一個函式,負責在堆上管理他們。在Go中有兩種記憶體分配策略,一個是小記憶體分配,一個是大記憶體分配。
小記憶體分配 Samll allocation
對於小記憶體分配,小於32kb,Go會試圖從本地快取mcache
中獲取。mcache
掌管一系列的記憶體大小為32kb的記憶體塊,我們稱這些記憶體塊為mspan
。這些mspan
裡麵包含空閒的,可用於記憶體分配的插槽塊。
每一個執行緒M
會指派一個處理者P
以及負責在同一時間最多隻處理一個goroutine。當需要分配記憶體的時候,我們併發的goruntine會使用當前P
上本地快取mspan
,去在mspan
上獲取第一個空閒的可用的插槽塊。使用本地快取不需要涉及鎖,因為同一時間只會執行一個goruntine,所以會讓記憶體分配非常高效。
mspan
分為從8位元組到32k位元組的大約70個大小的插槽塊,可以用於儲存不同大小的物件。
每一個塊存在兩次:一次是不包含指標的物件,一次是包含指標。這種區分能讓垃圾回收更加容易因為他不需要再去掃描那些不包含指標的物件。
在我們之前的例子中,結構體是32位元組大小,他會被填入一個32位元組大小的插槽塊中。
現在,我們會好奇,如果一個塊沒有足夠的空間可以分配的時候會發生什麼事情。Go維護了一個mcentral
,用於儲存mspan
中 不同插槽塊大小區間的連結串列集合。下面演示的是一個包含空閒物件以及不包含空閒物件的mcentral
。
mcentral
維護的是雙向連結串列,每個塊都有前一塊以及後一塊的指向。在非空連結串列中每個塊意味著至少有一個區域是空閒可以用於記憶體分配的,同時包含很多已經被使用的記憶體。其實,當垃圾回收清理記憶體時候,他可以清理一部分的塊,這些塊會被標記為不再被使用,然後將其放回非空連結串列中。
當我們想申請的記憶體已經被用完以後,可以向mcentral
申請獲取新的插槽塊:
當mcentral
中的插槽塊都已經被用完以後,Go需要一個途徑去給mcentral
獲取新的插槽塊,這時我們就會從堆上分配新的插槽塊並且鏈到我們的mcentral
上
這個堆會在必要的時候從作業系統中拉取記憶體。如果需要更多的記憶體,堆會分配一個大塊記憶體,稱為arena
,對於64位作業系統會分配64Mb的大記憶體,其他位數的作業系統會分配4Mb。arena
會將記憶體頁與mspan
建立對應關係。
大記憶體分配 Large allocation
Go的本地快取並不維護大記憶體,所以大於32kb的記憶體分配,會被四捨五入為一頁的大小,然後將也直接分配到堆上。
概述圖
現在我們已經對Go的記憶體管理與記憶體分配有了一個比較大概的理解了。我們將之前每個涉及到的部分整合起來,如下圖所示:
啟示
記憶體分配器最初是基於TCMalloc,TCMalloc是為Google建立的併發環境優化的記憶體分配器。 TCMalloc的檔案值得一讀,點我; 你還能在裡面找到一些前面我們所提及的一些專業術語的概念。