白話Go語言內存管理--內存分配原理

{"type":"doc","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"現代高級編程語言管理內存的方式分爲兩種:自動和手動,像C、C++ 等編程語言使用手動管理內存的方式,工程師編寫代碼過程中需要主動申請或者釋放內存;而 PHP、Java 和 Go 等語言使用自動的內存管理系統,有內存分配器和垃圾收集器來代爲分配和回收內存。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這篇文章會結合圖示用每個人都能聽懂的大白話解釋清楚Go的內存分配原理。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"關於Go的內存分配"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在"},{"type":"codeinline","content":[{"type":"text","text":"Go"}]},{"type":"text","text":"語言裏,從內存的分配到不再使用後內存的回收等等這些內存管理工作都是由"},{"type":"codeinline","content":[{"type":"text","text":"Go"}]},{"type":"text","text":"在底層完成的。雖然開發者在寫代碼時不必過度關心內存從分配到回收這個過程,但是"},{"type":"codeinline","content":[{"type":"text","text":"Go"}]},{"type":"text","text":"的內存分配策略裏有不少有意思的設計,通過了解他們有助於我們自身的提高,也讓我們能寫出更高效的"},{"type":"codeinline","content":[{"type":"text","text":"Go"}]},{"type":"text","text":"程序。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Go內存管理的設計旨在在併發環境中快速運行,並與垃圾回收器集成在一起。讓我們看一個簡單的示例:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"go"},"content":[{"type":"text","text":"package main\n\ntype smallStruct struct {\n a, b int64\n c, d float64\n}\n\nfunc main() {\n smallAllocation()\n}\n\n//go:noinline\nfunc smallAllocation() *smallStruct {\n return &smallStruct{}\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"函數上面的註釋"},{"type":"codeinline","content":[{"type":"text","text":"//go:noinline"}]},{"type":"text","text":"將禁止"},{"type":"codeinline","content":[{"type":"text","text":"Go"}]},{"type":"text","text":"對該函數進行內聯,這樣"},{"type":"codeinline","content":[{"type":"text","text":"main"}]},{"type":"text","text":"函數就會使用"},{"type":"codeinline","content":[{"type":"text","text":"smallAllocation"}]},{"type":"text","text":"函數返回的指針變量,因爲被多個函數使用,返回的這個變量將被分配到堆上。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"關於內聯的概念之前的文章有說過:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"內聯是一種手動或編譯器優化,用於將簡短函數的調用替換爲函數體本身。這麼做的原因是它可以消除函數調用本身的開銷,也使得編譯器能更高效地執行其他的優化策略。"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"所以如果上面的例子不干預編譯器的話,編譯器通過內聯將"},{"type":"codeinline","content":[{"type":"text","text":"smallAllocation"}]},{"type":"text","text":"函數體裏的內容直接放到"},{"type":"codeinline","content":[{"type":"text","text":"main"}]},{"type":"text","text":"函數裏,這樣就不會產生"},{"type":"codeinline","content":[{"type":"text","text":"smallAllocation"}]},{"type":"text","text":"這個函數的調用了,所有的變量都是"},{"type":"codeinline","content":[{"type":"text","text":"main"}]},{"type":"text","text":"函數內這個範圍使用的,也就不在需要將變量往堆上分配了。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"繼續說上面那個例子,通過逃逸分析命令 "},{"type":"text","marks":[{"type":"strong"}],"text":"go tool compile -m main.go"},{"type":"text","text":" 可以確認我們上面的分析,"},{"type":"codeinline","content":[{"type":"text","text":"&smallStruct{}"}]},{"type":"text","text":"會被分配到堆上去。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"shell"},"content":[{"type":"text","text":"➜ go tool compile -m main.go\nmain.go:12:6: can inline main\nmain.go:10:9: &smallStruct literal escapes to heap\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"藉助命令"},{"type":"text","marks":[{"type":"strong"}],"text":"go tool compile -S main.go"},{"type":"text","text":",可以顯示該程序的彙編代碼,也可以明確地向我們展示內存的分配:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"shell"},"content":[{"type":"text","text":"0x001d 00029 (main.go:10) LEAQ type.\"\".smallStruct(SB), AX\n0x0024 00036 (main.go:10) PCDATA $2, $0\n0x0024 00036 (main.go:10) MOVQ AX, (SP)\n0x0028 00040 (main.go:10) CALL runtime.newobject(SB)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"內置函數"},{"type":"codeinline","content":[{"type":"text","text":"newobject"}]},{"type":"text","text":"會通過調用另外一個內置函數"},{"type":"codeinline","content":[{"type":"text","text":"mallocgc"}]},{"type":"text","text":"在堆上分配新內存。在Go裏面有兩種內存分配策略,一種適用於程序裏小內存塊的申請,另一種適用於大內存塊的申請,大內存塊指的是大於32KB。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"下面我們來細聊一下這兩種策略。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"小於32KB內存塊的分配策略"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當程序裏發生了"},{"type":"codeinline","content":[{"type":"text","text":"32kb"}]},{"type":"text","text":"以下的小塊內存申請時,Go會從一個叫做的"},{"type":"codeinline","content":[{"type":"text","text":"mcache"}]},{"type":"text","text":"的本地緩存給程序分配內存。這個本地緩存"},{"type":"codeinline","content":[{"type":"text","text":"mcache"}]},{"type":"text","text":"持有一系列的大小爲"},{"type":"codeinline","content":[{"type":"text","text":"32kb"}]},{"type":"text","text":"的內存塊,這樣的一個內存塊裏叫做"},{"type":"codeinline","content":[{"type":"text","text":"mspan"}]},{"type":"text","text":",它是要給程序分配內存時的分配單元。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/9a/9ae47c3d72df87b6629a52bb3fd41978.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在Go的調度器模型裏,每個線程"},{"type":"codeinline","content":[{"type":"text","text":"M"}]},{"type":"text","text":"會綁定給一個處理器"},{"type":"codeinline","content":[{"type":"text","text":"P"}]},{"type":"text","text":",在單一粒度的時間裏只能做多處理運行一個"},{"type":"codeinline","content":[{"type":"text","text":"goroutine"}]},{"type":"text","text":",每個"},{"type":"codeinline","content":[{"type":"text","text":"P"}]},{"type":"text","text":"都會綁定一個上面說的本地緩存"},{"type":"codeinline","content":[{"type":"text","text":"mcache"}]},{"type":"text","text":"。當需要進行內存分配時,當前運行的"},{"type":"codeinline","content":[{"type":"text","text":"goroutine"}]},{"type":"text","text":"會從"},{"type":"codeinline","content":[{"type":"text","text":"mcache"}]},{"type":"text","text":"中查找可用的"},{"type":"codeinline","content":[{"type":"text","text":"mspan"}]},{"type":"text","text":"。從本地"},{"type":"codeinline","content":[{"type":"text","text":"mcache"}]},{"type":"text","text":"裏分配內存時不需要加鎖,這種分配策略效率更高。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"那麼有人就會問了,有的變量很小就是數字,有的卻是一個複雜的結構體,申請內存時都分給他們一個"},{"type":"codeinline","content":[{"type":"text","text":"mspan"}]},{"type":"text","text":"這樣的單元會不會產生浪費。其實"},{"type":"codeinline","content":[{"type":"text","text":"mcache"}]},{"type":"text","text":"持有的這一系列的"},{"type":"codeinline","content":[{"type":"text","text":"mspan"}]},{"type":"text","text":"並不都是統一大小的,而是按照大小,從8字節到32KB分了大概70類的"},{"type":"codeinline","content":[{"type":"text","text":"msapn"}]},{"type":"text","text":"。"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/57/57c039112191ee76bd8ac57750635afa.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"就文章開始的那個例子來說,那個結構體的大小是32字節,正好32字節的這種"},{"type":"codeinline","content":[{"type":"text","text":"mspan"}]},{"type":"text","text":"能滿足需求,那麼分配內存的時候就會給它分配一個32字節大小的"},{"type":"codeinline","content":[{"type":"text","text":"mspan"}]},{"type":"text","text":"。"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/54/5471f3483cf69df56d89ac21c29105c9.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"現在,我們可能會好奇,如果分配內存時"},{"type":"codeinline","content":[{"type":"text","text":"mcachce"}]},{"type":"text","text":"裏沒有空閒的32字節的"},{"type":"codeinline","content":[{"type":"text","text":"mspan"}]},{"type":"text","text":"了該怎麼辦?"},{"type":"codeinline","content":[{"type":"text","text":"Go"}]},{"type":"text","text":"裏還爲每種類別的"},{"type":"codeinline","content":[{"type":"text","text":"mspan"}]},{"type":"text","text":"維護着一個"},{"type":"codeinline","content":[{"type":"text","text":"mcentral"}]},{"type":"text","text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"mcentral"}]},{"type":"text","text":"的作用是爲所有"},{"type":"codeinline","content":[{"type":"text","text":"mcache"}]},{"type":"text","text":"提供切分好的"},{"type":"codeinline","content":[{"type":"text","text":"mspan"}]},{"type":"text","text":"資源。每個"},{"type":"codeinline","content":[{"type":"text","text":"central"}]},{"type":"text","text":"會持有一種特定大小的全局"},{"type":"codeinline","content":[{"type":"text","text":"mspan"}]},{"type":"text","text":"列表,包括已分配出去的和未分配出去的。 每個"},{"type":"codeinline","content":[{"type":"text","text":"mcentral"}]},{"type":"text","text":"對應一種"},{"type":"codeinline","content":[{"type":"text","text":"mspan"}]},{"type":"text","text":",當工作線程的"},{"type":"codeinline","content":[{"type":"text","text":"mcache"}]},{"type":"text","text":"中沒有合適(也就是特定大小的)的"},{"type":"codeinline","content":[{"type":"text","text":"mspan"}]},{"type":"text","text":"時就會從"},{"type":"codeinline","content":[{"type":"text","text":"mcentral"}]},{"type":"text","text":" 去獲取。"},{"type":"codeinline","content":[{"type":"text","text":"mcentral"}]},{"type":"text","text":"被所有的工作線程共同享有,存在多個"},{"type":"codeinline","content":[{"type":"text","text":"goroutine"}]},{"type":"text","text":"競爭的情況,因此從"},{"type":"codeinline","content":[{"type":"text","text":"mcentral"}]},{"type":"text","text":"獲取資源時需要加鎖。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"mcentral"}]},{"type":"text","text":"的定義如下:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"//runtime/mcentral.go\n\ntype mcentral struct {\n // 互斥鎖\n lock mutex \n \n // 規格\n sizeclass int32 \n \n // 尚有空閒object的mspan鏈表\n nonempty mSpanList \n \n // 沒有空閒object的mspan鏈表,或者是已被mcache取走的msapn鏈表\n empty mSpanList \n \n // 已累計分配的對象個數\n nmalloc uint64 \n}\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"mcentral"}]},{"type":"text","text":"裏維護着兩個雙向鏈表,"},{"type":"text","marks":[{"type":"strong"}],"text":"nonempty"},{"type":"text","text":"表示鏈表裏還有空閒的"},{"type":"codeinline","content":[{"type":"text","text":"mspan"}]},{"type":"text","text":"待分配。**empty**表示這條鏈表裏的"},{"type":"codeinline","content":[{"type":"text","text":"mspan"}]},{"type":"text","text":"都被分配了"},{"type":"codeinline","content":[{"type":"text","text":"object"}]},{"type":"text","text":"。"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/f5/f5110bef6f4597a437131121f65209f4.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果上面我們那個程序申請內存的時候,"},{"type":"codeinline","content":[{"type":"text","text":"mcache"}]},{"type":"text","text":"裏已經沒有合適的空閒"},{"type":"codeinline","content":[{"type":"text","text":"mspan"}]},{"type":"text","text":"了,那麼工作線程就會像下圖這樣去"},{"type":"codeinline","content":[{"type":"text","text":"mcentral"}]},{"type":"text","text":"裏去申請。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"簡單說下"},{"type":"codeinline","content":[{"type":"text","text":"mcache"}]},{"type":"text","text":"從"},{"type":"codeinline","content":[{"type":"text","text":"mcentral"}]},{"type":"text","text":"獲取和歸還"},{"type":"codeinline","content":[{"type":"text","text":"mspan"}]},{"type":"text","text":"的流程:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"獲取 加鎖;從"},{"type":"codeinline","content":[{"type":"text","text":"nonempty"}]},{"type":"text","text":"鏈表找到一個可用的"},{"type":"codeinline","content":[{"type":"text","text":"mspan"}]},{"type":"text","text":";並將其從"},{"type":"codeinline","content":[{"type":"text","text":"nonempty"}]},{"type":"text","text":"鏈表刪除;將取出的"},{"type":"codeinline","content":[{"type":"text","text":"mspan"}]},{"type":"text","text":"加入到"},{"type":"codeinline","content":[{"type":"text","text":"empty"}]},{"type":"text","text":"鏈表;將"},{"type":"codeinline","content":[{"type":"text","text":"mspan"}]},{"type":"text","text":"返回給工作線程;解鎖。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"歸還 加鎖;將"},{"type":"codeinline","content":[{"type":"text","text":"mspan"}]},{"type":"text","text":"從"},{"type":"codeinline","content":[{"type":"text","text":"empty"}]},{"type":"text","text":"鏈表刪除;將"},{"type":"codeinline","content":[{"type":"text","text":"mspan"}]},{"type":"text","text":"加入到"},{"type":"codeinline","content":[{"type":"text","text":"nonempty"}]},{"type":"text","text":"鏈表;解鎖。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/3d/3d1c2576f8fd97f2b14a25893d8d8d47.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當"},{"type":"codeinline","content":[{"type":"text","text":"mcentral"}]},{"type":"text","text":"沒有空閒的"},{"type":"codeinline","content":[{"type":"text","text":"mspan"}]},{"type":"text","text":"時,會向"},{"type":"codeinline","content":[{"type":"text","text":"mheap"}]},{"type":"text","text":"申請。而"},{"type":"codeinline","content":[{"type":"text","text":"mheap"}]},{"type":"text","text":"沒有資源時,會向操作系統申請新內存。"},{"type":"codeinline","content":[{"type":"text","text":"mheap"}]},{"type":"text","text":"主要用於大對象的內存分配,以及管理未切割的"},{"type":"codeinline","content":[{"type":"text","text":"mspan"}]},{"type":"text","text":",用於給"},{"type":"codeinline","content":[{"type":"text","text":"mcentral"}]},{"type":"text","text":"切割成小對象。"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/65/6563ed7a81234f85316ac9465d5570e1.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"同時我們也看到,"},{"type":"codeinline","content":[{"type":"text","text":"mheap"}]},{"type":"text","text":"中含有所有規格的"},{"type":"codeinline","content":[{"type":"text","text":"mcentral"}]},{"type":"text","text":",所以,當一個"},{"type":"codeinline","content":[{"type":"text","text":"mcache"}]},{"type":"text","text":"從"},{"type":"codeinline","content":[{"type":"text","text":"mcentral"}]},{"type":"text","text":"申請"},{"type":"codeinline","content":[{"type":"text","text":"mspan"}]},{"type":"text","text":"時,只需要在獨立的"},{"type":"codeinline","content":[{"type":"text","text":"mcentral"}]},{"type":"text","text":"中使用鎖,並不會影響申請其他規格的"},{"type":"codeinline","content":[{"type":"text","text":"mspan"}]},{"type":"text","text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上面說了每種尺寸的"},{"type":"codeinline","content":[{"type":"text","text":"mspan"}]},{"type":"text","text":"都有一個全局的列表存放在"},{"type":"codeinline","content":[{"type":"text","text":"mcentral"}]},{"type":"text","text":"裏供所有線程使用,所有"},{"type":"codeinline","content":[{"type":"text","text":"mcentral"}]},{"type":"text","text":"的集合則是存放於"},{"type":"codeinline","content":[{"type":"text","text":"mheap"}]},{"type":"text","text":"中的。 "},{"type":"codeinline","content":[{"type":"text","text":"mheap"}]},{"type":"text","text":"裏的"},{"type":"codeinline","content":[{"type":"text","text":"arena"}]},{"type":"text","text":" 區域是真正的堆區,運行時會將 "},{"type":"codeinline","content":[{"type":"text","text":"8KB"}]},{"type":"text","text":" 看做一頁,這些內存頁中存儲了所有在堆上初始化的對象。運行時使用二維的 "},{"type":"link","attrs":{"href":"https://github.com/golang/go/blob/e7f9e17b7927cad7a93c5785e864799e8d9b4381/src/runtime/mheap.go#L217","title":""},"content":[{"type":"text","text":"runtime.heapArena"}]},{"type":"text","text":" 數組管理所有的內存,每個 "},{"type":"codeinline","content":[{"type":"text","text":"runtime.heapArena"}]},{"type":"text","text":" 都會管理 64MB 的內存。"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/ac/acf45ff994e25f720e5695c0116eb02c.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果 "},{"type":"codeinline","content":[{"type":"text","text":"arena"}]},{"type":"text","text":" 區域沒有足夠的空間,會調用 "},{"type":"link","attrs":{"href":"https://github.com/golang/go/blob/6dd11bcb35cba37f5994c1b9aaaf7d2dc13fd7cf/src/runtime/malloc.go#L617","title":""},"content":[{"type":"text","text":"runtime.mheap.sysAlloc"}]},{"type":"text","text":" 從操作系統中申請更多的內存。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"大於32KB內存塊的分配策略"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Go沒法使用工作線程的本地緩存"},{"type":"codeinline","content":[{"type":"text","text":"mcache"}]},{"type":"text","text":"和全局中心緩存"},{"type":"codeinline","content":[{"type":"text","text":"mcentral"}]},{"type":"text","text":"上管理超過32KB的內存分配,所以對於那些超過32KB的內存申請,會直接從堆上("},{"type":"codeinline","content":[{"type":"text","text":"mheap"}]},{"type":"text","text":")上分配對應的數量的內存頁(每頁大小是8KB)給程序。"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/5a/5a1bddc841eb177b0a6c280b76279f79.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"總結"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們把內存分配管理涉及的所有概念串起來,可以勾畫出Go內存管理的一個全局視圖:"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/5f/5f19bbede074d83d2d28295cfb9500ab.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Go語言的內存分配非常複雜,這個文章從一個比較粗的角度來看Go的內存分配,並沒有深入細節。一般而言,瞭解它的原理,到這個程度也就可以了(應付面試)。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"總結起來關於Go內存分配管理的策略有如下幾點:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Go在程序啓動時,會向操作系統申請一大塊內存,由"},{"type":"codeinline","content":[{"type":"text","text":"mheap"}]},{"type":"text","text":"結構全局管理。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Go內存管理的基本單元是"},{"type":"codeinline","content":[{"type":"text","text":"mspan"}]},{"type":"text","text":",每種"},{"type":"codeinline","content":[{"type":"text","text":"mspan"}]},{"type":"text","text":"可以分配特定大小的"},{"type":"codeinline","content":[{"type":"text","text":"object"}]},{"type":"text","text":"。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"mcache"}]},{"type":"text","text":", "},{"type":"codeinline","content":[{"type":"text","text":"mcentral"}]},{"type":"text","text":", "},{"type":"codeinline","content":[{"type":"text","text":"mheap"}]},{"type":"text","text":"是"},{"type":"codeinline","content":[{"type":"text","text":"Go"}]},{"type":"text","text":"內存管理的三大組件,"},{"type":"codeinline","content":[{"type":"text","text":"mcache"}]},{"type":"text","text":"管理線程在本地緩存的"},{"type":"codeinline","content":[{"type":"text","text":"mspan"}]},{"type":"text","text":";"},{"type":"codeinline","content":[{"type":"text","text":"mcentral"}]},{"type":"text","text":"管理全局的"},{"type":"codeinline","content":[{"type":"text","text":"mspan"}]},{"type":"text","text":"供所有線程使用;"},{"type":"codeinline","content":[{"type":"text","text":"mheap"}]},{"type":"text","text":"管理"},{"type":"codeinline","content":[{"type":"text","text":"Go"}]},{"type":"text","text":"的所有動態分配內存。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"一般小對象通過"},{"type":"codeinline","content":[{"type":"text","text":"mspan"}]},{"type":"text","text":"分配內存;大對象則直接由"},{"type":"codeinline","content":[{"type":"text","text":"mheap"}]},{"type":"text","text":"分配內存。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"相關閱讀"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://mp.weixin.qq.com/s?_biz=MzUzNTY5MzU2MA==&mid=2247485579&idx=1&sn=f481cff4ffccacc186a020e45e884924&chksm=fa80d91ccdf7500ab984ecde7056aa29a2986c423557736c32bd251f2cb5d1ce1e54909753cc&token=1603590138&lang=zhCN#rd","title":""},"content":[{"type":"text","text":"Go內存管理之代碼的逃逸分析"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://mp.weixin.qq.com/s?_biz=MzUzNTY5MzU2MA==&mid=2247484559&idx=1&sn=d639ce831d514c0f73f195f207c0583d&chksm=fa80d518cdf75c0e224697bd9f6f97ad94082f09591d2b9141974baf8a1054e9de0d895fb603&token=1603590138&lang=zhCN#rd","title":""},"content":[{"type":"text","text":"上週併發題的解題思路以及介紹Go語言調度器"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"參考鏈接"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://medium.com/a-journey-with-go/go-memory-management-and-allocation-a7396d430f44","title":""},"content":[{"type":"text","text":" Memory Management and Allocation"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://juejin.im/post/6844903795739082760#heading-7","title":""},"content":[{"type":"text","text":"圖解Go語言內存分配"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://draveness.me/golang/docs/part3-runtime/ch07-memory/golang-memory-allocator/","title":""},"content":[{"type":"text","text":"內存分配器"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"感謝你閱讀我的文章,微信搜索公衆號「網管叨bi叨」第一時間獲取更多原創圖解Go語言原理的文章。"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章