網站首頁 編程語言 正文
1.什么是內存逃逸
在一段程序中,每一個函數都會有自己的內存區域分配自己的局部變量,返回值,這些內存會由編譯器在棧中進行分配,每一個函數會分配一個棧幀,在函數運行結束后銷毀,但是有些變量我們想在函數運行結束后仍然使用,就需要把這個變量分配在堆上,這種從“棧”上逃逸到“堆”上的現象叫做內存逃逸
2.什么是逃逸分析
雖然Go語言引入的Gc,GC機制會對堆上的對象進行管理,當某個對象不可達(沒有其他對象引用他),他將會被回收。雖然GC可以降低工作人員負擔,但是GC也會給程序帶來性能損耗,當堆內存上有大量的堆內存對象,就會給GC很大的壓力,雖然Go語言使用的是標記清除算法,并且在此基礎上使用了三色標記法和寫屏障技術,但是我們在堆上分配大量內存,仍然會對GC造成很大壓力,Go引入了逃逸分析,就是想減少堆內存的分配,可以在棧分配的內存盡量分配在棧上
3.小結
逃逸分析就是在程序編譯階段根據代碼中的數據流,對代碼中哪些變量需要在棧上分配,哪些需要在對象分配的靜態分析方法,堆和棧相比,堆適合分配不可預知大小的內存,但是付出代價是分配速度慢,容易產生碎片,棧分配十分快,棧分配只需要兩個指令“Push”和"Release"分配和釋放,而且堆分配需要先找一塊適合大小的內存塊分配,需要垃圾回收釋放,所以逃逸分析可以更好的做內存分配
Go語言的逃逸分析
src/cmd/compile/internal/gc/escape.go
- pointers to stack objects cannot be stored in the heap: 指向棧對象的指針不能存儲在堆中
- pointers to a stack object cannot outlive that object:指向棧對象的指針不能超過該對象的存活期,指針不能在棧對象銷毀之后依然存活(例子:聲明的函數返回并銷毀了對象的棧幀,或者它在循環迭代中被重復用于邏輯上不同的變量)
既然逃逸分析是在編譯階段進行的,那我們就可以通過go build -gcflga '-m -m l'查看逃逸分析結果
4.逃逸分析案例
1.函數返回局部指針變量
func Add(x,y int) *int { res := 0 res = x + y return &res } func main() { Add(1,2) }
.\pointer.go:4:2: res escapes to heap:
.\pointer.go:4:2: ? flow: ~r2 = &res:
.\pointer.go:4:2: ? ? from &res (address-of) at .\pointer.go:6:9
.\pointer.go:4:2: ? ? from return &res (return) at .\pointer.go:6:2
.\pointer.go:4:2: moved to heap: res
函數返回局部變量是一個指針變量,函數Add執行結束,對應棧幀就會銷毀,但是引用返回到函數外部,如果我們外部解析地址,就會導致程序訪問非法內存,所以經過編輯器分析過后將其在堆上分配
2.interface類型逃逸
1.interface產生逃逸
func main() { str := "荔枝" fmt.Println(str) }
E:\GoStudy\src\HighBase\Escape>go build -gcflags="-m -m -l" ./pointer.go
# command-line-arguments
.\pointer.go:20:13: str escapes to heap:
.\pointer.go:20:13: ? flow: {storage for ... argument} = &{storage for str}:
.\pointer.go:20:13: ? ? from str (spill) at .\pointer.go:20:13
.\pointer.go:20:13: ? ? from ... argument (slice-literal-element) at .\pointer.go:20:13
.\pointer.go:20:13: ? flow: {heap} = {storage for ... argument}:
.\pointer.go:20:13: ? ? from ... argument (spill) at .\pointer.go:20:13
.\pointer.go:20:13: ? ? from fmt.Println(... argument...) (call parameter) at .\pointer.go:20:13
.\pointer.go:20:13: ... argument does not escape
.\pointer.go:20:13: str escapes to heap
str是main的一個局部變量,傳給 fmt.Printl()之后逃逸,因為fmt.Println()的入參是interface{}類型,如果參數為interface{},那么編譯期間就很難確定參數類型
2.指向棧對象的指針不能在堆中
我們把代碼改成這樣
func main() { str := "蘇珊" fmt.Println(&str) }
# command-line-arguments
.\pointer.go:19:2: str escapes to heap:
.\pointer.go:19:2: ? flow: {storage for ... argument} = &str:
.\pointer.go:19:2: ? ? from &str (address-of) at .\pointer.go:20:14
.\pointer.go:19:2: ? ? from &str (interface-converted) at .\pointer.go:20:14
.\pointer.go:19:2: ? ? from ... argument (slice-literal-element) at .\pointer.go:20:13
.\pointer.go:19:2: ? flow: {heap} = {storage for ... argument}:
.\pointer.go:19:2: ? ? from ... argument (spill) at .\pointer.go:20:13
.\pointer.go:19:2: ? ? from fmt.Println(... argument...) (call parameter) at .\pointer.go:20:13
.\pointer.go:19:2: moved to heap: str
.\pointer.go:20:13: ... argument does not escape
這次str也逃逸到堆上面了,在堆上面進行分配,因為入參是interface,變量str的地址被以實參的方式傳入fmt.Println被裝箱到一個interface{}
裝箱的形參變量要在堆上分配,但是還需要存儲一個棧上的地址,這和之前說的第一條不符,所以str也會分配到堆上
3.閉包產生逃逸
func Increase() func() int { ?n := 0 ?return func() int { ? n++ ? return n ?} } func main() { ?in := Increase() ?fmt.Println(in()) // 1 }
E:\GoStudy\src\HighBase\Escape>go build -gcflags "-m -m -l" ./pointer.go
# command-line-arguments
.\pointer.go:27:2: Increase capturing by ref: n (addr=false assign=true width=8)
.\pointer.go:28:9: func literal escapes to heap:
.\pointer.go:28:9: ? flow: ~r0 = &{storage for func literal}:
.\pointer.go:28:9: ? ? from func literal (spill) at .\pointer.go:28:9
.\pointer.go:28:9: ? ? from return func literal (return) at .\pointer.go:28:2
.\pointer.go:27:2: n escapes to heap:
.\pointer.go:27:2: ? flow: {storage for func literal} = &n:
.\pointer.go:27:2: ? ? from n (captured by a closure) at .\pointer.go:29:3
.\pointer.go:27:2: ? ? from n (reference) at .\pointer.go:29:3
.\pointer.go:27:2: moved to heap: n
.\pointer.go:28:9: func literal escapes to heap
.\pointer.go:36:16: in() escapes to heap:
.\pointer.go:36:16: ? flow: {storage for ... argument} = &{storage for in()}:
.\pointer.go:36:16: ? ? from in() (spill) at .\pointer.go:36:16
.\pointer.go:36:16: ? ? from ... argument (slice-literal-element) at .\pointer.go:36:13
.\pointer.go:36:16: ? flow: {heap} = {storage for ... argument}:
.\pointer.go:36:16: ? ? from ... argument (spill) at .\pointer.go:36:13
.\pointer.go:36:16: ? ? from fmt.Println(... argument...) (call parameter) at .\pointer.go:36:13
.\pointer.go:36:13: ... argument does not escape
.\pointer.go:36:16: in() escapes to heap
因為函數是指針類型,所以匿名函數當做返回值產生逃逸,匿名函數使用外部變量n,這個n會一直存在知道in被銷毀
4. 變量大小不確定及棧空間不足引發逃逸
import ( ?? ?"math/rand" ) func LessThan8192() ?{ ?? ?nums := make([]int, 100) // = 64KB ?? ?for i := 0; i < len(nums); i++ { ?? ??? ?nums[i] = rand.Int() ?? ?} } func MoreThan8192(){ ?? ?nums := make([]int, 1000000) // = 64KB ?? ?for i := 0; i < len(nums); i++ { ?? ??? ?nums[i] = rand.Int() ?? ?} } func NonConstant() { ?? ?number := 10 ?? ?s := make([]int, number) ?? ?for i := 0; i < len(s); i++ { ?? ??? ?s[i] = i ?? ?} } func main() { ?? ?NonConstant() ?? ?MoreThan8192() ?? ?LessThan8192() }
# command-line-arguments
.\pointer.go:43:14: make([]int, 100) does not escape
.\pointer.go:51:14: make([]int, 1000000) escapes to heap:
.\pointer.go:51:14: ? flow: {heap} = &{storage for make([]int, 1000000)}:
.\pointer.go:51:14: ? ? from make([]int, 1000000) (too large for stack) at .\pointer.go:51:14
.\pointer.go:51:14: make([]int, 1000000) escapes to heap
.\pointer.go:60:11: make([]int, number) escapes to heap:
.\pointer.go:60:11: ? flow: {heap} = &{storage for make([]int, number)}:
.\pointer.go:60:11: ? ? from make([]int, number) (non-constant size) at .\pointer.go:60:11
.\pointer.go:60:11: make([]int, number) escapes to heap
棧空間足夠不會發生逃逸,但是變量過大,已經超過棧空間,會逃逸到堆上
5.總結
- 逃逸分析在編譯階段確定哪些變量可以分配在棧中,哪些變量分配在堆上
- 逃逸分析減輕了GC壓力,提高程序的運行速度
- 棧上內存使用完畢不需要GC處理,堆上內存使用完畢會交給GC處理
- 函數傳參時對于需要修改原對象值,或占用內存比較大的結構體,選擇傳指針。對于只讀的占用內存較小的結構體,直接傳值能夠獲得更好的性能
- 根據代碼具體分析,盡量減少逃逸代碼,減輕GC壓力,提高性能
原文鏈接:https://blog.csdn.net/weixin_45802793/article/details/125633869
相關推薦
- 2022-12-05 Android實現自動變換大小的ViewPager_Android
- 2022-06-14 利用Python?NumPy庫及Matplotlib庫繪制數學函數圖像_python
- 2022-06-01 C#中內聯函數的用法介紹_C#教程
- 2022-08-23 Django配合python進行requests請求的問題及解決方法_python
- 2024-02-16 SpringBoot的默認組件掃描
- 2022-10-10 C++淺析程序中內存的分布_C 語言
- 2022-10-11 Android之InstanceState詳解
- 2022-06-29 詳解Shell腳本中^M的問題和解決方案_linux shell
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支