一文帶你輕松理解Go中的內(nèi)存逃逸問題
內(nèi)存逃逸是什么
- 在程序中,每個函數(shù)塊都會有自己的內(nèi)存區(qū)域用來存自己的局部變量(內(nèi)存占用少)、返回地址、返回值之類的數(shù)據(jù),這一塊內(nèi)存區(qū)域有特定的結(jié)構(gòu)和尋址方式,尋址起來十分迅速,開銷很少。這一塊內(nèi)存地址稱為棧。
- 棧是線程級別的,大小在創(chuàng)建的時候已經(jīng)確定,當變量太大的時候,會"逃逸"到堆上,這種現(xiàn)象稱為內(nèi)存逃逸。
- 簡單來說,局部變量通過堆分配和回收,就叫內(nèi)存逃逸。
內(nèi)存逃逸危害
- 堆是一塊沒有特定結(jié)構(gòu),也沒有固定大小的內(nèi)存區(qū)域,可以根據(jù)需要進行調(diào)整。
- 全局變量,內(nèi)存占用較大的局部變量,函數(shù)調(diào)用結(jié)束后不能立刻回收的局部變量都會存在堆里面。
- 變量在堆上的分配和回收都比在棧上開銷大的多。
- 對于 go 這種帶 GC 的語言來說,會增加 gc 壓力,同時也容易造成內(nèi)存碎片。
內(nèi)存逃逸現(xiàn)象
- 向 channel 發(fā)送指針數(shù)據(jù)。因為在編譯時,不知道channel中的數(shù)據(jù)會被哪個 goroutine 接收,因此編譯器沒法知道變量什么時候才會被釋放,因此只能放入堆中。
- 局部變量在函數(shù)調(diào)用結(jié)束后還被其他地方使用,比如函數(shù)返回局部變量指針或閉包中引用包外的值。因為變量的生命周期可能會超過函數(shù)周期,因此只能放入堆中。
- 在 slice 或 map 中存儲指針。比如 []*string,其后面的數(shù)組可能是在棧上分配的,但其引用的值還是在堆上。
- 切片擴容后長度太大,導致??臻g不足,逃逸到堆上。
- 在 interface 類型上調(diào)用方法。 在 interface 類型上調(diào)用方法時會把interface變量使用堆分配, 因為方法的真正實現(xiàn)只能在運行時知道。
逃逸分析原則
- 編譯階段無法確定的參數(shù),會逃逸到堆上;
- 變量在函數(shù)外部存在引用,會逃逸到堆上;不存在引用,則會繼續(xù)在棧上;
- 變量占用內(nèi)存較大時,會逃逸到堆上;
內(nèi)存逃逸解決
- 對于小型的數(shù)據(jù),使用傳值而不是傳指針,避免內(nèi)存逃逸。
- 避免使用長度不固定的slice切片,在編譯期無法確定切片長度,只能將切片使用堆分配。
- interface調(diào)用方法會發(fā)生內(nèi)存逃逸,在熱點代碼片段,謹慎使用。
避免內(nèi)存逃逸需要遵循如下兩個原則: - 指向棧對象上的指針不能被存儲到堆中。
- 指向棧對象上的指針不能超過該棧對象的聲明周期。
具體案例
參數(shù)為interface類型會逃逸
下面通過舉例,來進一步論證逃逸分析的原則,加深一下理解
我們可以使用這個命令go build -gcflags '-m -m -l' go文件名,來查看逃逸分析的結(jié)果。
func main() {
num := 1
fmt.Println(num)
}原因分析:
func Println(a ...interface{}) (n int, err error),這個函數(shù)的入?yún)⑹?code>interface類型,編譯階段無法確定其具體的參數(shù)類型,所以內(nèi)存分配到堆上
變量在函數(shù)外部有引用會逃逸
func main() {
_ = test()
}
func test() *int {
num := 10
return &num
}原因分析:
變量num在函數(shù)外部存在引用,函數(shù)退出時棧中的內(nèi)存(棧幀)已經(jīng)釋放,但引用已經(jīng)被返回,如果通過引用地址取值,在棧中是取不到值的,所以Go為了避免這個情況,會將內(nèi)存分配到堆上。
變量占用內(nèi)存較大時會逃逸
func main() {
//不會逃逸
s1 := make([]int, 10, 10)
for i := 0; i < 10; i++ {
s1[i] = i
}
//會逃逸
s2 := make([]int, 10000, 10000)
for i := 0; i < 10000; i++ {
s2[i] = i
}
}原因分析:
切片容量過大時,會產(chǎn)生逃逸,內(nèi)存分配到堆上;容量小時,不會逃逸,內(nèi)存分配依賴在棧上。
變量大小不確定時會逃逸
func main() {
num := 10
s := make([]int, num, num)
for i := 0; i < num; i++ {
s[i] = i
}
}原因分析:
切片的長度和容量,雖然通過聲明的變量num來指定了,但在編譯階段是未知的,并不確定num的具體值,所以會逃逸,將內(nèi)存分配到堆上。
到此這篇關(guān)于一文帶你輕松理解Go中的內(nèi)存逃逸問題的文章就介紹到這了,更多相關(guān)Go 內(nèi)存逃逸內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Golang中文字符串截取函數(shù)實現(xiàn)原理
在golang中可以通過切片截取一個數(shù)組或字符串,但是當截取的字符串是中文時,可能會出現(xiàn)問題,下面我們來自定義個函數(shù)解決Golang中文字符串截取問題2018-03-03
解決golang post文件時Content-Type出現(xiàn)的問題
這篇文章主要介紹了解決golang post文件時Content-Type出現(xiàn)的問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-05-05
golang封裝一個執(zhí)行命令行的函數(shù)(return?stderr/stdout/exitcode)示例代碼
在?Go?語言中,您可以使用?os/exec?包來執(zhí)行外部命令,不通過調(diào)用?shell,并且能夠獲得進程的退出碼、標準輸出和標準錯誤輸出,下面給大家分享golang封裝一個執(zhí)行命令行的函數(shù)(return?stderr/stdout/exitcode)的方法,感興趣的朋友跟隨小編一起看看吧2024-06-06

