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