<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                # Golang內存分配 ## 1引言 golang是谷歌2009年發布的開源編程語言,截止目前go的release版本已經到了1.12,golang語言專門針對多處理器系統應用程序的編程進行了優化,使用golang編譯的程序可以媲美C /C++代碼的速度,而且更加安全、支持并行進程。和其他“高級語言”一樣,golang同樣有一套自己的內存管理機制,自主的去完成內存分配、垃圾回收、內存管理等過程,從而避免頻繁的向操作系統申請、釋放內存,有效的提升go語言的處理性能。由于篇幅有限,本文重點針對golang1.12.6版本就內存分配情況進行一下梳理和講解。golang的內存管理是基于tcmalloc模型設計,但又有些差異,局部緩存并不是分配給進程或者線程,而是分配給P(Processor);golang的GC是stop the world,并不是每個進程單獨進行GC;golang語言對span的管理更有效率。 在進入正題之前,我們先回顧一下c語言內存是如何申請的,常用方式是調用malloc函數,指定要分配的大小,直接向操作系統申請,那我們來思考一下這種方式有沒有什么問題?它會涉及到用戶態和內核態的切換過程,那么頻繁的進行用戶態和內核態切換就會帶來很大的耗時,導致性能下降,因此我們必須從語言層面找一種方式減少這么操作,那就是自己做一套內存管理機制。 就內存管理來說,如果要讓我們去設計,思考一下都需要哪些功能模塊才能保證高效穩定? * 內存池:要減少用戶態和內核態的頻繁切換就需要自己申請一塊內存空間,將之分割成大小規格不同的內存塊來供程序使用,內存池是再適合不過的組成部分了。 * GC:內存管理不光需要使用方便,還要保證內存使用過程能夠節約,畢竟整個系統的內存資源是有限的,那么就需要GC進行動態的垃圾回收,銷毀無用的對象,釋放內存來保證整個程序乃至系統運行平穩。 * 鎖:一個應用程序內部之間存在大量的線程,線程之間資源是共享的,那么要保證同一塊內存使用過程不出現復用或者污染,就必須保證同一時間只能有一個線程進行申請,第一個想到的肯定是鎖,對公共區域的資源一定要加鎖,另一種方式就是內存隔離,這個在golang的mcache中會有體現。 下面我們進入正題,基于上面分析的問題對golang進行一下研究,看看golang到底怎么管理內存的。 ## 2基本概念 ### 1.什么是span 首先我們來介紹一下span的概念,span是golang內存管理的基本單位,每個span管理指定規格(以page為單位)的內存塊,內存池分配出不同規格的內存塊就是通過span體現出來的,應用程序創建對象就是通過找到對應規格的span來存儲的,下面我們看一下mspan的結構。 go1.12.6\src\runtime\mheap.go ![1](https://img.kancloud.cn/cc/64/cc6464dedae6c570aaa83569728f9234_830x856.jpg) ![2](https://img.kancloud.cn/2c/54/2c5475b55177e256b2580128969789c7_830x469.jpg) 根據源碼和上圖結合來看,會更加容易理解mspan,每一個mspan就是用來給程序分配對象空間的,也就是說一般我們對象都會放到mspan中管理,這里我們重點解釋一下如圖所示的幾個屬性,startAddr是該mspan在arena區域的首地址,freeindex用來表示下一個可能是空對象的位置,也就是說freeindex之前的元素(存儲對象的空間)均是已經被使用的,freeindex之后的元素可能被使用可能沒被使用,allocCache是從freeindex開始對后續元素分配情況進行緩存標記,通過freeindex和allocCache結合進行查找未分配的元素位置效率會更高,我們能快速的找到一個空對象分配給程序使用,而不用全局遍歷。allocBits用來標識該span中所有元素的使用分配情況,gcmarkBits用來sweep過程進行標記垃圾對象的,用于后續gc。 ### 2.怎么區分span 那么要想區分不同規格的span,我們必須要有一個標識,每個span通過splanclass標識屬于哪種規格的span,golang的span規格一共有67種,具體查看 go1.12.6\src\runtime\sizeclasses.go,可看到下圖的規格表 ![3](https://img.kancloud.cn/c5/50/c5501eefa020bf8b18a4d8b8d218fc1c_830x693.jpg) 其中: * class:分類id或者規格id,也就是spanclass,表示該span可存儲的對象規格類型 * bytes/obj:該列代表能存儲每個對象的字節數,也就是說可以存儲多大的對象,字段是elemsize * bytes/span:每個span占用堆的字節數,也即頁數*頁大小,npages*8KB * objects:每個span可分配的元素個數,或者說可存儲的對象個數,也就是nelems,也即(bytes/spans)/(bytes/obj) * tail bytes:每個span產生的內存碎片,也即(bytes/span)%(bytes/obj) * max waste:最大浪費比例,(bytes/obj-最小使用量)*objects/(bytes/span)*100,比如classId=2最小使用量是9bytes,則max waste=(16-9)*512/8192*100=43.75% 通過上表,我們可以很清楚的知道在創建一個對象時候,需要去選哪一個splanclass的span去獲取內存空間,一個span能存多少這樣大小的對象等等信息,非常清晰而又盡可能節約的去使用內存。另外上表可見最大的對象是32KB大小,超過32KB大小的由特殊的class表示,該class ID為0,每個class只包含一個對象。所以上面只有列出了1-66。 ## 3內存管理組件 闡述完一些基本概念,我們可以知道對象是存在span中,大家肯定會疑惑那span放在哪,怎么把這些各種規格孤立的span串起來?下面我們來說一下golang的內存管理組件,內存分配是由內存分配器完成,分配器由3種組件構成:mcache、mcentral、mheap,我們來詳細講一下每個組件。 我們知道golang之所有有很強的并發能力,依賴于它的G-P-M并發模型, ![4](https://img.kancloud.cn/34/a8/34a8e59591cbbd988dfff3c77cfc9b63_830x202.jpg) ### 1.mcache mcache就綁在并發模型的P上,也就是說我們每一個P都會有一個mcahe綁定,用來給協程分配對象存儲空間的。下面具體看一下mcache的結構 go1.12.6\src\runtime\mcache.go ![5](https://img.kancloud.cn/27/d2/27d2375a16b4b7d4bd6c0a8ff3608352_831x491.jpg) 可以看到在mcache結構體中并沒有鎖存在,這是因為每個P都會綁定一個mcache,而每個P同時只會處理一個groutine,而且不同P之間是內存隔離的,因此不存在競爭情況。關鍵字段都已經在代碼中解釋了,這里我們重點關注一下alloc [numSpanClasses]*mspan,由于SpanClasses一共有67種,為了滿足指針對象和非指針對象,這里為每種規格的span同時準備scan和noscan兩個,因此一共有134個mspan緩存鏈表,分別用于存儲指針對象和非指針對象,這樣對非指針對象掃描的時候不需要繼續掃描它是否引用其他對象,GC掃描對象的時候對于noscan的span可以不去查看bitmap區域來標記子對象,這樣可以大幅提升標記的效率。另外mcache在初始化時是沒有任何mspan資源的,在使用過程中會動態地申請,不斷的去填充alloc[numSpanClasses]*mspan,通過雙向鏈表連接,如下圖所示: ![6](https://img.kancloud.cn/21/42/2142cedd34033ec5b12de1963b581a68_830x469.jpg) 通過圖示我們可以看到alloc[numSpanClasses]*mspan管理了很多不同規格不同類型的span,golang對于[16B,32KB]的對象會使用這部分span進行內存分配,所以所有在這區間大小的對象都會從alloc這個數組里尋找,看下源碼: ![7](https://img.kancloud.cn/14/e8/14e8bdc631b8ad8c5676a082e81fa96e_830x344.jpg) 而對于更小的對象,我們叫它tiny對象,golang會通過tiny和tinyoffset組合尋找位置分配內存空間,這樣可以更好的節約空間。源碼如下: ![8](https://img.kancloud.cn/61/a7/61a730790e2f9ed51a868e3817f7a464_831x654.jpg) ### 2.mcentral 剛才我們提到mcache中的mspan都是動態申請的,那到底是去哪里申請呢?其實當空間不足的時候,mcache會去mcentral中申請對應規格的mspan,我們來繼續看一下mcentral,先來看一下結構,go1.12.6\src\runtime\mcentral.go ![9](https://img.kancloud.cn/9f/71/9f71be4167e6cab388d3ca2a7350c807_830x294.jpg) 看到mcentral的結構體會覺得很簡單,首先與mcache有一個明顯區別,就是有鎖存在,由于mcentral是公共資源,會有多個mcache向它申請mspan,因此必須加鎖,另外,mcentral與mcache不同,由于P綁定了很多Goroutine,在P上會處理不同大小的對象,mcache就需要包含各種規格的mspan,但mcentral不同,同一個mcentral只負責一種規格的mspan就夠了,mcache就像一個市政府,mcentral就像國家部委,市政府需要管管轄區域內的所有方面的事情,而每個部委很專一,只管一方面,市政府需要哪方面資源,就去和對應部委對接就可以了。mcentral也是用spanclass進行標記規格類型,該規格的所有未被使用的空閑mspan會掛載到nonempty鏈表上,已經被mcache拿走,未歸還的會掛載到empty鏈表上,歸還后會再掛載到nonempty上,用圖表示如下,以規格sizeClass=1為例: ![10](https://img.kancloud.cn/29/39/2939c52d5930179836f11875d7df3e1c_830x374.jpg) 每一個mSpanList都掛著同一規格mspan雙向鏈表,當然這個鏈表也不是固定大小的,都會動態變化的。 ### 3.mheap mcentral的nonempty也有用完的時候,當nonempty為空,再被申請的時候,也就是mcentral空間不足了,那么它會向mheap申請新的頁,下面我們看一下mheap結構。 go1.12.6\src\runtime\mheap.go ![11](https://img.kancloud.cn/44/63/44638e68b288c82d6c1c2c18d5cbe20d_668x1393.jpg) 通個看這個結構,可以感覺到mheap相對復雜一些,重要字段我已經在代碼中注釋,我們知道每個golang程序啟動時候會向操作系統申請一塊虛擬內存空間,僅僅是虛擬內存空間,真正需要的時候才會發生缺頁中斷,向系統申請真正的物理空間,在golang1.11版本以后,申請的內存空間會放在一個heapArena數組里,由arenas [1 << arenaL1Bits]*[1 << arenaL2Bits]*heapArena表示,用于應用程序內存分配,下面展示一下數組中一塊heapArena虛擬內存空間區域分配, ![12](https://img.kancloud.cn/02/98/0298f129d405300e9fb6af7e3893f559_534x92.jpg) 分為三個區域,分別是: * spans區域:存放span指針地址的地方,每個指針大小是8Byte * bitmap區域:用于標記arena區域中哪些地址保存了對象,并且對象中哪些地址包含了指針,主要用于GC * arena區域:heap區域,程序內存分配的地方,管理的最小基本單位是頁,golang一個page的大小是:8KB 下面看一下每個區域的大小情況 heapArena結構體如下: ![13](https://img.kancloud.cn/37/a1/37a14a017167804558d542ddbf1c3069_830x201.jpg) 關鍵字段計算定義如下: ![14](https://img.kancloud.cn/9f/97/9f97bd036e3c3827b4098d20b31d7c4b_830x292.jpg) 通過源碼可以看出spans大小等于arenaSize/8KB,可以理解為有多少page就準備出對應數量的“地址格子”,來充分保證能存下所有的span地址。 對于bitmap區域,由于bitmap是用來標記每個地址空間的使用情況,我們知道指針大小是8Byte,因此需要arenaSize/8個,一個bitmap可以標記四個地址,因此再除4。 ![15](https://img.kancloud.cn/e1/23/e123f33c1fed90072401bbcce292a654_830x589.jpg) 如上圖所示,是bitmap區域一個字節對arena區域的標記情況情況,高四位標記四個內存地址使用情況,低四位標記存儲的是否是指針。對于arenaSize,根據源碼公式,在64位非windows系統分配大小是64MB,windows 64位是4MB。 介紹完三個區域,我們再來看一下central [numSpanClasses],它就是管理的所有規格mcentral的集合,同樣是134種,pad對齊填充用于確保mcentrals以CacheLineSize個字節數分隔,所以每一個MCentral.lock都可以獲取自己的緩存行。而fixalloc類型的相關成員都是用來分配span、mache等對象的內存分配器,這里大家不要搞暈,具體來講,以span舉例,每一個span也需要空間存儲,這個就是在spanalloc這個二叉樹堆上存儲,拿到這個對象,將startAddr指向arena區域內的npages的內存空間才是給mcache使用的,或者說給P進行對象分配的。另外,由于mheap也是公共資源,一定也要有鎖的存在。 下面結合圖看一下: ![16](https://img.kancloud.cn/59/8f/598f33649d755cf6a8b21e0e9cfeacc6_830x569.jpg) 從上圖可以更清楚的看到,一個mheap會有134種mcentral,而每一種規格的mcentral會掛載該規格的mspan鏈表。 前面我們講過tiny對象和小對象的內存分配,那大于32KB的對象怎么辦呢?golang將大于32KB的對象定義為大對象,直接通過mheap分配。這些大對象的申請是以一個全局鎖為代價的,所以同時只能服務一個P申請,大對象內存分配一定是頁(8KB)的整數倍。結合源碼再看一下: ![17](https://img.kancloud.cn/0a/e3/0ae373748b2a6125585f1db5ea427390_830x488.jpg) 可以看出不管多大對象,一切的空間都是從mheap獲取的,那mheap要是不足了呢?就只能向操作系統申請了。 ## 4內存分配規則 講完內存管理組件,我們再來總結一下內存分配規則: * tiny對象內存分配,直接向mcache的tiny對象分配器申請,如果空間不足,則向mcache的tinySpanClass規格的span鏈表申請,如果沒有,則向mcentral申請對應規格mspan,依舊沒有,則向mheap申請,最后都用光則向操作系統申請。 * 小對象內存分配,先向本線程mcache申請,發現mspan沒有空閑的空間,向mcentral申請對應規格的mspan,如果mcentral對應規格沒有,向mheap申請對應頁初始化新的mspan,如果也沒有,則向操作系統申請,分配頁。 * 大對象內存分配,直接向mheap申請spanclass=0,如果沒有則向操作系統申請。 流程圖如下: ![18](https://img.kancloud.cn/d0/ca/d0ca767b6e71d1adea13d1de62e86975_829x728.jpg) 部分內存申請源碼源碼如下: mcache向mcentral申請,調用go1.12.6\src\runtime\mcache.go refill方法 ![19](https://img.kancloud.cn/9e/f2/9ef237eb3d0bfa6d7c1e999bc2f34f4a_830x937.jpg) mcentral空間不足,向mheap申請分配頁創建新的mspan,調用go1.12.6\src\runtime\mcental.go grow方法 ![20](https://img.kancloud.cn/18/b0/18b041da91b3dfca5318e24066d4f603_830x521.jpg) mheap空間不足會調用go1.12.6\src\runtime\mheap.go grow方法進行系統申請 ![21](https://img.kancloud.cn/a1/df/a1df2290caa487af65f514cff23fbeb1_829x565.jpg) gc改進 通過上節流程圖和代碼,我們可以清晰的知道一個對象內存申請的整個過程,那思考一下這個流程是否完善,我們都知道golang通過gc進行垃圾回收,而完整的gc需要兩次stop the world,如果我們完全依賴gc去垃圾回收是不是影響整個程序的性能,我們假設一個場景,mcentral的span一直不夠用,那會不斷的去向mheap去申請page空間,導致mheap的使用率很快就觸發到gc的閾值,啟動gc處理過程,頻繁的gc就會導致頻繁的程序停服,極大的會影響程序服務性能,那golang的做法是怎么樣的呢?,在1.12版本里面golang對mheap結構添加了reclaimCredit成員變量,每次mcentral向mheap申請新的page空間創建span的時候,都會先去掃描arenas里面的heapArena,去清理垃圾對象回收相同page數量的空間,由于掃描到的垃圾對象不可能正好等于相同page,多清理的page大小就會存到到reclaimCredit里面,下一次再掃描arenas的時候會先去抵消reclaimCredit,如果不夠才會去掃描heapArena。通過這種方式有效的防止mheap使用率過快增長,下面是整個流程圖: ![22](https://img.kancloud.cn/97/da/97da53d333b1872f1a2b94c1be5438a0_830x733.jpg) 同理,我們知道當mheap不夠用的時候,會去向操作系統申請內存空間,如果增長過快,也會造成整個操作系統的不穩定,golang對這部分也做了處理,1.12版本mheap引入scavengeCredit這個成員變量,當向操作系統申請內存空間的時候,會先去掃描free這個二叉樹堆,span從大到小的掃描,釋放所需大小的空間給os,多余釋放的到小會存儲到scavengeCredit中,下次再次掃描的時候會先扣除這個值。下面是整個流程圖: ![23](https://img.kancloud.cn/4a/a9/4aa9d93bc7fef97b1730fe5ecda5ba7d_830x750.jpg) ## 5結尾 到此也就基本講完了golang的內存分配的整個環節,本文也是受php內存管理啟發,進行了一下golang源碼深入研究,由于篇幅關系并沒有把源碼中的各種細節進行詳細講解,僅對整體流程進行梳理和闡述,對關鍵源碼進行注釋和解釋,希望能給對golang感興趣的伙伴給予一定幫助,如需更具體的了解,可以根據這個大流程進行源碼學習。本文基于1.12.6版本源碼一點點梳理,不足之處還請各位不吝雅正。 (文/朱清偉 編/張利利)
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看