成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

面試官:小松子來聊一聊內(nèi)存逃逸

開發(fā) 后端
初次看到這個(gè)話題,我是懵逼的,怎么還有內(nèi)存逃逸,內(nèi)存逃逸到底是干什么的?接下來我們一起來看看什么是內(nèi)存逃逸。

[[414656]]

本文轉(zhuǎn)載自微信公眾號「Golang夢工廠」,作者AsongGo。轉(zhuǎn)載本文請聯(lián)系Golang夢工廠公眾號。

前言

哈嘍,大家好,我是asong。最近無聊看了一下Go語言的面試八股文,發(fā)現(xiàn)面試官都喜歡問內(nèi)存逃逸這個(gè)話題,這個(gè)激起了我的興趣,我對內(nèi)存逃逸的了解很淺,所以找了很多文章精讀了一下,在這里做一個(gè)總結(jié),方便日后查閱、學(xué)習(xí)。

什么是內(nèi)存逃逸

初次看到這個(gè)話題,我是懵逼的,怎么還有內(nèi)存逃逸,內(nèi)存逃逸到底是干什么的?接下來我們一起來看看什么是內(nèi)存逃逸。

我們都知道一般情況下程序存放在rom或者Flash中,運(yùn)行時(shí)需要拷貝到內(nèi)存中執(zhí)行,內(nèi)存會(huì)分別存儲(chǔ)不同的信息,內(nèi)存空間包含兩個(gè)最重要的區(qū)域:堆區(qū)(Stack)和棧區(qū)(Heap),對于我這種C語言出身的人,對堆內(nèi)存和棧內(nèi)存的了解還是挺深的。在C語言中,棧區(qū)域會(huì)專門存放函數(shù)的參數(shù)、局部變量等,棧的地址從內(nèi)存高地址往低地址增長,而堆內(nèi)存正好相反,堆地址從內(nèi)存低地址往高地址增長,但是如果我們想在堆區(qū)域分配內(nèi)存需要我們手動(dòng)調(diào)用malloc函數(shù)去堆區(qū)域申請內(nèi)存分配,然后我使用完了還需要自己手動(dòng)釋放,如果沒有釋放就會(huì)導(dǎo)致內(nèi)存泄漏。寫過C語言的朋友應(yīng)該都知道C語言函數(shù)是不能返回局部變量地址(特指存放于棧區(qū)的局部變量地址),除非是局部靜態(tài)變量地址,字符串常量地址、動(dòng)態(tài)分配地址。其原因是一般局部變量的作用域只在函數(shù)內(nèi),其存儲(chǔ)位置在棧區(qū)中,當(dāng)程序調(diào)用完函數(shù)后,局部變量會(huì)隨此函數(shù)一起被釋放。其地址指向的內(nèi)容不明(原先的數(shù)值可能不變,也可能改變)。而局部靜態(tài)變量地址和字符串常量地址存放在數(shù)據(jù)區(qū),動(dòng)態(tài)分配地址存放在堆區(qū),函數(shù)運(yùn)行結(jié)束后只會(huì)釋放棧區(qū)的內(nèi)容,而不會(huì)改變數(shù)據(jù)區(qū)和堆區(qū)。

所以在C語言中我們想在一個(gè)函數(shù)中返回局部變量地址時(shí),有三個(gè)正確的方式:返回靜態(tài)局部變量地址、返回字符串常量地址,返回動(dòng)態(tài)分配在堆上的地址,因?yàn)樗麄兌疾辉跅^(qū),即使釋放函數(shù),其內(nèi)容也不會(huì)受影響,我們以在返回堆上內(nèi)存地址為例看一段代碼:

  1. #include "stdio.h" 
  2. #include "stdlib.h" 
  3. //返回動(dòng)態(tài)分配的地址  
  4. int* f1() 
  5.     int a = 9; 
  6.     int *pa = (int*) malloc(8); 
  7.     *pa = a; 
  8.     return pa; 
  9.  
  10. int main() 
  11.     int *pb; 
  12.     pb = f1(); 
  13.     printf("after : *pb = %d\tpb = %p\n",*pb, pb); 
  14.     free(pb); 
  15.     return 1; 

通過上面的例子我們知道在C語言中動(dòng)態(tài)內(nèi)存的分配與釋放完全交與程序員的手中,這樣就會(huì)導(dǎo)致我們在寫程序時(shí)如履薄冰,好處是我們可以完全掌控內(nèi)存,缺點(diǎn)是我們一不小心就會(huì)導(dǎo)致內(nèi)存泄漏,所以很多現(xiàn)代語言都有GC機(jī)制,Go就是一門帶垃圾回收的語言,真正解放了我們程序員的雙手,我們不需要在像寫C語言那樣考慮是否能返回局部變量地址了,內(nèi)存管理交與給編譯器,編譯器會(huì)經(jīng)過逃逸分析把變量合理的分配到"正確"的地方。

說到這里,可以簡單總結(jié)一下什么是內(nèi)存逃逸了:

在一段程序中,每一個(gè)函數(shù)都會(huì)有自己的內(nèi)存區(qū)域存放自己的局部變量、返回地址等,這些內(nèi)存會(huì)由編譯器在棧中進(jìn)行分配,每一個(gè)函數(shù)都會(huì)分配一個(gè)棧楨,在函數(shù)運(yùn)行結(jié)束后進(jìn)行銷毀,但是有些變量我們想在函數(shù)運(yùn)行結(jié)束后仍然使用它,那么就需要把這個(gè)變量在堆上分配,這種從"棧"上逃逸到"堆"上的現(xiàn)象就成為內(nèi)存逃逸。

什么是逃逸分析

上面我們知道了什么是內(nèi)存逃逸,下面我們就來看一看什么是逃逸分析?

上文我們說到C語言使用malloc在堆上動(dòng)態(tài)分配內(nèi)存后,還需要手動(dòng)調(diào)用free釋放內(nèi)存,如果不釋放就會(huì)造成內(nèi)存泄漏的風(fēng)險(xiǎn)。在Go語言中堆內(nèi)存的分配與釋放完全不需要我們?nèi)ス芰耍珿o語言引入了GC機(jī)制,GC機(jī)制會(huì)對位于堆上的對象進(jìn)行自動(dòng)管理,當(dāng)某個(gè)對象不可達(dá)時(shí)(即沒有其對象引用它時(shí)),他將會(huì)被回收并被重用。雖然引入GC可以讓開發(fā)人員降低對內(nèi)存管理的心智負(fù)擔(dān),但是GC也會(huì)給程序帶來性能損耗,當(dāng)堆內(nèi)存中有大量待掃描的堆內(nèi)存對象時(shí),將會(huì)給GC帶來過大的壓力,雖然Go語言使用的是標(biāo)記清除算法,并且在此基礎(chǔ)上使用了三色標(biāo)記法和寫屏障技術(shù),提高了效率,但是如果我們的程序仍在堆上分配了大量內(nèi)存,依賴會(huì)對GC造成不可忽視的壓力。因此為了減少GC造成的壓力,Go語言引入了逃逸分析,也就是想法設(shè)法盡量減少在堆上的內(nèi)存分配,可以在棧中分配的變量盡量留在棧中。

小結(jié)逃逸分析:

逃逸分析就是指程序在編譯階段根據(jù)代碼中的數(shù)據(jù)流,對代碼中哪些變量需要在棧中分配,哪些變量需要在堆上分配進(jìn)行靜態(tài)分析的方法。堆和棧相比,堆適合不可預(yù)知大小的內(nèi)存分配。但是為此付出的代價(jià)是分配速度較慢,而且會(huì)形成內(nèi)存碎片。棧內(nèi)存分配則會(huì)非常快。棧分配內(nèi)存只需要兩個(gè)CPU指令:“PUSH”和“RELEASE”,分配和釋放;而堆分配內(nèi)存首先需要去找到一塊大小合適的內(nèi)存塊,之后要通過垃圾回收才能釋放。所以逃逸分析更做到更好內(nèi)存分配,提高程序的運(yùn)行速度。

Go語言中的逃逸分析

Go語言的逃逸分析總共實(shí)現(xiàn)了兩個(gè)版本:

  • 1.13版本前是第一版
  • 1.13版本后是第二版

粗略看了一下逃逸分析的代碼,大概有1500+行(go1.15.7)。代碼我倒是沒仔細(xì)看,注釋我倒是仔細(xì)看了一遍,注釋寫的還是很詳細(xì)的,代碼路徑:src/cmd/compile/internal/gc/escape.go,大家可以自己看一遍注釋,其逃逸分析原理如下:

  • pointers to stack objects cannot be stored in the heap:指向棧對象的指針不能存儲(chǔ)在堆中
  • pointers to a stack object cannot outlive that object:指向棧對象的指針不能超過該對象的存活期,也就說指針不能在棧對象被銷毀后依舊存活。(例子:聲明的函數(shù)返回并銷毀了對象的棧幀,或者它在循環(huán)迭代中被重復(fù)用于邏輯上不同的變量)

我們大概知道它的分析準(zhǔn)則是什么就好了,具體逃逸分析是怎么做的,感興趣的同學(xué)可以根據(jù)源碼自行研究。

既然逃逸分析是在編譯階段進(jìn)行的,那我們就可以通過go build -gcflags '-m -m -l'命令查看到逃逸分析的結(jié)果,我們之前在分析內(nèi)聯(lián)優(yōu)化時(shí)使用的-gcflags '-m -m',能看到所有的編譯器優(yōu)化,這里使用-l禁用掉內(nèi)聯(lián)優(yōu)化,只關(guān)注逃逸優(yōu)化就好了。

現(xiàn)在我們也知道了逃逸分析,接下來我們就看幾個(gè)逃逸分析的例子。

幾個(gè)逃逸分析的例子

1. 函數(shù)返回局部指針變量

先看例子:

  1. #include "stdio.h" 
  2. #include "stdlib.h" 
  3. //返回動(dòng)態(tài)分配的地址  
  4. int* f1() 
  5.     int a = 9; 
  6.     int *pa = (int*) malloc(8); 
  7.     *pa = a; 
  8.     return pa; 
  9.  
  10. int main() 
  11.     int *pb; 
  12.     pb = f1(); 
  13.     printf("after : *pb = %d\tpb = %p\n",*pb, pb); 
  14.     free(pb); 
  15.     return 1; 

查看逃逸分析結(jié)果:

  1. go build -gcflags="-m -m -l" ./test1.go 
  2. # command-line-arguments 
  3. ./test1.go:6:9: &res escapes to heap 
  4. ./test1.go:6:9:         from ~r2 (returnat ./test1.go:6:2 
  5. ./test1.go:4:2: moved to heap: res 

分析結(jié)果很明了,函數(shù)返回的局部變量是一個(gè)指針變量,當(dāng)函數(shù)Add執(zhí)行結(jié)束后,對應(yīng)的棧楨就會(huì)被銷毀,但是引用已經(jīng)返回到函數(shù)之外,如果我們在外部解引用地址,就會(huì)導(dǎo)致程序訪問非法內(nèi)存,就像上面的C語言的例子一樣,所以編譯器經(jīng)過逃逸分析后將其在堆上分配內(nèi)存。

2. interface類型逃逸

先看一個(gè)例子:

  1. func main()  { 
  2.  str := "asong太帥了吧" 
  3.  fmt.Printf("%v",str) 

查看逃逸分析結(jié)果:

  1. go build -gcflags="-m -m -l" ./test2.go  
  2. # command-line-arguments 
  3. ./test2.go:9:13: str escapes to heap 
  4. ./test2.go:9:13:        from ... argument (arg to ...) at ./test2.go:9:13 
  5. ./test2.go:9:13:        from *(... argument) (indirection) at ./test2.go:9:13 
  6. ./test2.go:9:13:        from ... argument (passed to call[argument content escapes]) at ./test2.go:9:13 
  7. ./test2.go:9:13: main ... argument does not escape 

str是main函數(shù)中的一個(gè)局部變量,傳遞給fmt.Println()函數(shù)后發(fā)生了逃逸,這是因?yàn)閒mt.Println()函數(shù)的入?yún)⑹且粋€(gè)interface{}類型,如果函數(shù)參數(shù)為interface{},那么在編譯期間就很難確定其參數(shù)的具體類型,也會(huì)發(fā)送逃逸。

觀察這個(gè)分析結(jié)果,我們可以看到?jīng)]有moved to heap: str,這也就是說明str變量并沒有在堆上進(jìn)行分配,只是它存儲(chǔ)的值逃逸到堆上了,也就說任何被str引用的對象必須分配在堆上。如果我們把代碼改成這樣:

  1. func main()  { 
  2.  str := "asong太帥了吧" 
  3.  fmt.Printf("%p",&str) 

查看逃逸分析結(jié)果:

  1. go build -gcflags="-m -m -l" ./test2.go 
  2. # command-line-arguments 
  3. ./test2.go:9:18: &str escapes to heap 
  4. ./test2.go:9:18:        from ... argument (arg to ...) at ./test2.go:9:12 
  5. ./test2.go:9:18:        from *(... argument) (indirection) at ./test2.go:9:12 
  6. ./test2.go:9:18:        from ... argument (passed to call[argument content escapes]) at ./test2.go:9:12 
  7. ./test2.go:9:18: &str escapes to heap 
  8. ./test2.go:9:18:        from &str (interface-converted) at ./test2.go:9:18 
  9. ./test2.go:9:18:        from ... argument (arg to ...) at ./test2.go:9:12 
  10. ./test2.go:9:18:        from *(... argument) (indirection) at ./test2.go:9:12 
  11. ./test2.go:9:18:        from ... argument (passed to call[argument content escapes]) at ./test2.go:9:12 
  12. ./test2.go:8:2: moved to heap: str 
  13. ./test2.go:9:12: main ... argument does not escape 

這回str也逃逸到了堆上,在堆上進(jìn)行內(nèi)存分配,這是因?yàn)槲覀冊L問str的地址,因?yàn)槿雲(yún)⑹莍nterface類型,所以變量str的地址以實(shí)參的形式傳入fmt.Printf后被裝箱到一個(gè)interface{}形參變量中,裝箱的形參變量的值要在堆上分配,但是還要存儲(chǔ)一個(gè)棧上的地址,也就是str的地址,堆上的對象不能存儲(chǔ)一個(gè)棧上的地址,所以str也逃逸到堆上,在堆上分配內(nèi)存。(這里注意一個(gè)知識點(diǎn):Go語言的參數(shù)傳遞只有值傳遞)

3. 閉包產(chǎn)生的逃逸

  1. func Increase() func() int { 
  2.  n := 0 
  3.  return func() int { 
  4.   n++ 
  5.   return n 
  6.  } 
  7.  
  8. func main() { 
  9.  in := Increase() 
  10.  fmt.Println(in()) // 1 

查看逃逸分析結(jié)果:

  1. go build -gcflags="-m -m -l" ./test3.go 
  2. # command-line-arguments 
  3. ./test3.go:10:3: Increase.func1 capturing by ref: n (addr=true assign=true width=8) 
  4. ./test3.go:9:9: func literal escapes to heap 
  5. ./test3.go:9:9:         from ~r0 (assigned) at ./test3.go:7:17 
  6. ./test3.go:9:9: func literal escapes to heap 
  7. ./test3.go:9:9:         from &(func literal) (address-ofat ./test3.go:9:9 
  8. ./test3.go:9:9:         from ~r0 (assigned) at ./test3.go:7:17 
  9. ./test3.go:10:3: &n escapes to heap 
  10. ./test3.go:10:3:        from func literal (captured by a closure) at ./test3.go:9:9 
  11. ./test3.go:10:3:        from &(func literal) (address-ofat ./test3.go:9:9 
  12. ./test3.go:10:3:        from ~r0 (assigned) at ./test3.go:7:17 
  13. ./test3.go:8:2: moved to heap: n 
  14. ./test3.go:17:16: in() escapes to heap 
  15. ./test3.go:17:16:       from ... argument (arg to ...) at ./test3.go:17:13 
  16. ./test3.go:17:16:       from *(... argument) (indirection) at ./test3.go:17:13 
  17. ./test3.go:17:16:       from ... argument (passed to call[argument content escapes]) at ./test3.go:17:13 
  18. ./test3.go:17:13: main ... argument does not escape 

因?yàn)楹瘮?shù)也是一個(gè)指針類型,所以匿名函數(shù)當(dāng)作返回值時(shí)也發(fā)生了逃逸,在匿名函數(shù)中使用外部變量n,這個(gè)變量n會(huì)一直存在直到in被銷毀,所以n變量逃逸到了堆上。

4. 變量大小不確定及棧空間不足引發(fā)逃逸

我們先使用ulimit -a查看操作系統(tǒng)的棧空間:

  1. ulimit -a 
  2. -t: cpu time (seconds)              unlimited 
  3. -f: file size (blocks)              unlimited 
  4. -d: data seg size (kbytes)          unlimited 
  5. -s: stack size (kbytes)             8192 
  6. -c: core file size (blocks)         0 
  7. -v: address space (kbytes)          unlimited 
  8. -l: locked-in-memory size (kbytes)  unlimited 
  9. -u: processes                       2784 
  10. -n: file descriptors                256 

我的電腦的棧空間大小是8192,所以根據(jù)這個(gè)我們寫一個(gè)測試用例:

  1. package main 
  2.  
  3. import ( 
  4.  "math/rand" 
  5.  
  6. func LessThan8192()  { 
  7.  nums := make([]int, 100) // = 64KB 
  8.  for i := 0; i < len(nums); i++ { 
  9.   nums[i] = rand.Int() 
  10.  } 
  11.  
  12.  
  13. func MoreThan8192(){ 
  14.  nums := make([]int, 1000000) // = 64KB 
  15.  for i := 0; i < len(nums); i++ { 
  16.   nums[i] = rand.Int() 
  17.  } 
  18.  
  19.  
  20. func NonConstant() { 
  21.  number := 10 
  22.  s := make([]int, number) 
  23.  for i := 0; i < len(s); i++ { 
  24.   s[i] = i 
  25.  } 
  26.  
  27. func main() { 
  28.  NonConstant() 
  29.  MoreThan8192() 
  30.  LessThan8192() 

查看逃逸分析結(jié)果:

  1. go build -gcflags="-m -m -l" ./test4.go 
  2. # command-line-arguments 
  3. ./test4.go:8:14: LessThan8192 make([]int, 100) does not escape 
  4. ./test4.go:16:14: make([]int, 1000000) escapes to heap 
  5. ./test4.go:16:14:       from make([]int, 1000000) (non-constant sizeat ./test4.go:16:14 
  6. ./test4.go:25:11: make([]int, number) escapes to heap 
  7. ./test4.go:25:11:       from make([]int, number) (non-constant sizeat ./test4.go:25:11 

我們可以看到,當(dāng)棧空間足夠時(shí),不會(huì)發(fā)生逃逸,但是當(dāng)變量過大時(shí),已經(jīng)完全超過棧空間的大小時(shí),將會(huì)發(fā)生逃逸到堆上分配內(nèi)存。

同樣當(dāng)我們初始化切片時(shí),沒有直接指定大小,而是填入的變量,這種情況為了保證內(nèi)存的安全,編譯器也會(huì)觸發(fā)逃逸,在堆上進(jìn)行分配內(nèi)存。

參考文章(建議大家閱讀一遍)

  • https://driverzhang.github.io/post/golang%E5%86%85%E5%AD%98%E5%88%86%E9%85%8D%E9%80%83%E9%80%B8%E5%88%86%E6%9E%90/
  • https://segmentfault.com/a/1190000039843497
  • https://tonybai.com/2021/05/24/understand-go-escape-analysis-by-example/
  • https://cloud.tencent.com/developer/article/1732263
  • https://geektutu.com/post/hpg-escape-analysis.html

總結(jié)

本文到這里結(jié)束了,這篇文章我們一起分析了什么是內(nèi)存逃逸以及Go語言中的逃逸分析,上面只列舉了幾個(gè)例子,因?yàn)榘l(fā)生的逃逸的情況是列舉不全的,我們只需要了解什么是逃逸分析,了解逃逸的策略就可以了,后面在實(shí)戰(zhàn)中可以根據(jù)具體代碼具體分析,寫出更優(yōu)質(zhì)的代碼。

最后對逃逸做一個(gè)總結(jié): 

  • 逃逸分析在編譯階段確定哪些變量可以分配在棧中,哪些變量分配在堆上
  • 逃逸分析減輕了GC壓力,提高程序的運(yùn)行速度
  • 棧上內(nèi)存使用完畢不需要GC處理,堆上內(nèi)存使用完畢會(huì)交給GC處理
  • 函數(shù)傳參時(shí)對于需要修改原對象值,或占用內(nèi)存比較大的結(jié)構(gòu)體,選擇傳指針。對于只讀的占用內(nèi)存較小的結(jié)構(gòu)體,直接傳值能夠獲得更好的性能
  • 根據(jù)代碼具體分析,盡量減少逃逸代碼,減輕GC壓力,提高性能

 

責(zé)任編輯:武曉燕 來源: Golang夢工廠
相關(guān)推薦

2020-06-28 09:30:37

Linux內(nèi)存操作系統(tǒng)

2019-03-21 11:04:22

安全標(biāo)準(zhǔn)信息

2023-03-06 21:23:23

Redis數(shù)據(jù)庫

2022-05-12 23:19:15

Redis內(nèi)存碎片處理

2021-04-20 08:40:11

內(nèi)存管理Lwip

2022-05-18 16:35:43

Redis內(nèi)存運(yùn)維

2019-03-20 14:29:46

Linux虛擬內(nèi)存

2022-10-19 15:20:58

pandas數(shù)據(jù)處理庫技巧

2022-09-19 16:24:33

數(shù)據(jù)可視化Matplotlib工具

2023-02-09 08:48:47

Java虛擬機(jī)

2022-10-21 07:59:17

CSS滾動(dòng)文字特效

2018-06-07 13:17:12

契約測試單元測試API測試

2023-09-22 17:36:37

2021-01-28 22:31:33

分組密碼算法

2020-05-22 08:16:07

PONGPONXG-PON

2023-03-03 12:37:50

JavaJVM內(nèi)存溢出

2020-08-12 08:34:16

開發(fā)安全We

2022-11-26 00:00:06

裝飾者模式Component

2022-10-08 11:33:56

邊緣計(jì)算云計(jì)算

2018-01-10 14:13:04

測試矩陣API測試
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號

主站蜘蛛池模板: 在线观看国产视频 | 久久r免费视频 | 91亚洲精品久久久电影 | av一级| 99热在线免费 | 亚洲影音先锋 | 日屁网站 | 久久伊 | 日韩成人在线网站 | 97精品国产 | 九九福利 | 精品视频一区在线 | 国产精品呻吟久久av凹凸 | 久久久久国产一区二区三区四区 | 18性欧美 | 国产婷婷精品 | 欧美特级黄色 | 亚洲欧美一区二区三区情侣bbw | 日韩精品一区二区三区中文在线 | 刘亦菲国产毛片bd | 久久久久国产精品人 | 欧美男人天堂 | 精品视频一二区 | 午夜欧美一区二区三区在线播放 | 午夜一区二区三区视频 | 午夜免费看视频 | 国产精品日韩高清伦字幕搜索 | 成人毛片在线视频 | 久久99深爱久久99精品 | 成人在线视频免费播放 | 国产在线一区二区三区 | 颜色网站在线观看 | 色资源在线观看 | 美女视频黄色的 | 91免费观看国产 | 国产综合精品一区二区三区 | 日韩在线一区二区 | 国产a区 | 国产日韩精品视频 | 日韩欧美日韩在线 | 在线观看亚洲精品视频 |