這個題是小編面試遇到次數(shù)最多的題目之一了。在開始之前,我們先思考以下幾個問題,當(dāng)然,后面小編也會一一解答。 1,什么是內(nèi)存逃逸。 2,內(nèi)存逃逸的場景有哪些。 3,分析內(nèi)存逃逸的意義。 4,怎么避免內(nèi)存逃逸。 什么是內(nèi)存逃逸在了解什么是內(nèi)存逃逸之前,我們先來簡單地熟悉一下兩個概念。棧內(nèi)存和堆內(nèi)存。本次主要是講述的是Golang的內(nèi)存逃逸,故而關(guān)于內(nèi)存分配和垃圾回收就不做贅述了。后面小編會單獨(dú)出兩篇來寫這個,有需要的同學(xué)可以關(guān)注小編。關(guān)于這一塊,我們現(xiàn)在只需要了解三點(diǎn)。
有了前面的基礎(chǔ)知識,那我們簡單粗暴地介紹一下內(nèi)存逃逸。一個對象本應(yīng)該分配在棧上面,結(jié)果分配在了堆上面,這就是內(nèi)存逃逸。如下 內(nèi)存逃逸的場景有哪些要了解內(nèi)存逃逸的場景,首先我們要學(xué)會怎么分析內(nèi)存逃逸。其實(shí)分析起來很簡單,只需要一條簡單的命令,即gcflags。這個是有很多參數(shù)的,此處只舉一個最基本的例子。 go build -gcflags '-m' main.go 接下來我們就來討論一下內(nèi)存逃逸的場景有哪些。常見的場景有四種,小編總結(jié)為:局部指針返回,棧空間不足,動態(tài)類型,閉包引用。 局部指針返回 當(dāng)我們在某個方法內(nèi)定義了一個局部指針,并且將這個指針作為返回值返回時(shí),此時(shí)就發(fā)生了逃逸。這種類型的逃逸是比較常見的,如下。
??臻g不足 眾所周知,在系統(tǒng)中棧空間相比與總的內(nèi)存來說是非常小的。如下,小編的Mac是16G*512G的,可是整個系統(tǒng)中棧空間大小也才8M。 而在我們的實(shí)際編碼過程中,大部分Goroutine的占用空間不到10KB(這也是Golang能支持高并發(fā)的原因之一)。而其中分配給棧的更是少之又少。所以一旦某個對象體積過大時(shí)候就會發(fā)生逃逸,從棧上面轉(zhuǎn)到堆上面。 如下,有兩個map,space1和space2,space1長度大小都是100,space2長度大小都是10000,結(jié)果space2發(fā)生了逃逸,space1沒有。 package mainimport ( 'fmt')func main() { space() fmt.Println('更多免費(fèi)資料,關(guān)注公眾號:不穿格子衫的程序猿')}// 棧空間溢出func space() { // 不溢出 space1 := make([]int, 100, 100) for i := 0; i < len(space1); i++ { space1[i] = i } // 溢出 space2 := make([]int, 10000, 10000) for i := 0; i < len(space2); i++ { space2[i] = i }} 動態(tài)類型 小編認(rèn)為,這種內(nèi)存逃逸應(yīng)該是最多的,最常見的,而且還無法避免。簡單地說就是被調(diào)用函數(shù)的入?yún)⑹莍nterface或者是不定參數(shù),此時(shí)就會發(fā)生內(nèi)存逃逸。如下:
哈哈哈,同學(xué)們是不是大跌眼鏡,一個簡簡單單的Println居然也會發(fā)生內(nèi)存逃逸。那么問題來了,這個是怎么導(dǎo)致的呢,廢話不多說,直接拔掉底褲擼源碼。此處就是所謂的動態(tài)類型。 閉包調(diào)用 首先說一下,這種場景是非常少的,一般沒有人寫這種可讀性這么差的代碼,小編這串代碼都是參考別人的。所以小編認(rèn)為,這種場景,我們只需要知道即可,大概率是碰不上的。 package mainimport ( 'fmt')func main() { fmt.Println(closure())}// 閉包逃逸func closure() func() string { return func() string { return '更多免費(fèi)資料,關(guān)注公眾號:不穿格子衫的程序猿' }} 分析內(nèi)存逃逸的意義前面給大家列舉了四種內(nèi)存逃逸的場景,那么問題來了,分析內(nèi)存逃逸有什么用呢?簡單的總結(jié)就是兩點(diǎn):減輕GC的壓力,提高分配速度。 上文已經(jīng)說過,Golang的GC主要是針對堆的,而不是棧。試想一下,如果大量的對象從棧逃逸到堆上,是不是就會增加GC的壓力。在GC的過程中會占用比較大的系統(tǒng)開銷(一般可達(dá)到CPU容量的25%)。而且目前所有的GC都有STW這個死結(jié),而STW會造成用戶直觀的'卡頓'。非常影響用戶體驗(yàn)。 此外,堆和棧相比,堆適合不可預(yù)知大小的內(nèi)存分配。但是為此付出的代價(jià)是分配速度較慢,而且會形成內(nèi)存碎片。棧內(nèi)存分配則會非???。棧分配內(nèi)存只需要兩個CPU指令:“PUSH”和“RELEASE”,分配和釋放;而堆內(nèi)存分配首先需要去找到一塊大小合適的內(nèi)存塊,之后要通過垃圾回收才能釋放。 通過逃逸分析,可以盡量把那些不需要分配到堆上的變量直接分配到棧上,堆上的變量少了,會減輕分配堆內(nèi)存的開銷,同時(shí)也會減少GC的壓力,提高程序的運(yùn)行速度。 怎么避免內(nèi)存逃逸最后說一下怎么避免內(nèi)存逃逸吧。首先需要注意的是,Golang在編譯的時(shí)候就可以確立逃逸,并不需要等到運(yùn)行時(shí)。這樣就給了咱們避免內(nèi)存逃逸的機(jī)會。 首先咱們明確一點(diǎn),小編認(rèn)為沒有任何方式能絕對避免內(nèi)存逃逸。原因嘛,就是存在【動態(tài)類型】這種逃逸方式,幾乎所有的庫函數(shù)都是動態(tài)類型的。當(dāng)然也不是說咱么要破罐子破摔,該避免還是要避免一下的,主要的原則有以下幾種,分別針對上面幾種場景。
資料環(huán)節(jié)又到了大家期待的福利時(shí)間了。本次贈送的是Golang實(shí)戰(zhàn)案例20份。 廢話不多說,各位看官大人要怎么獲取呢。很簡單,關(guān)注小編,私信「資料」即可獲得免費(fèi)獲取方式。 |
|