golang 系列:神祕的記憶體管理

hjavn發表於2021-08-02

工具與資源中心

幫助開發者更加高效的工作,提供圍繞開發者全生命週期的工具與資源
developer.aliyun.com/tool?spm=a1z3...

一、概述

記憶體管理在任何的程式語言裡都是重頭戲,Golang 也不例外。Go 借鑑了 Google 的 TCMalloc,它是高效能的用於 c++ 的記憶體分配器。其核心思想是記憶體池 + 多級物件管理 ,能加快分配速度,降低資源競爭。

二、基礎結構

在 Go 裡用於記憶體管理的物件結構主要是下面幾個:

mheap、mspan、arenas、mcentral、mcache。

其中,mspan 是一個基礎結構,分配記憶體時,基本以它為單位

mcache、mcentral、mheap 起到了記憶體池的作用,會被預分配記憶體,當有對應大小的物件需要分配時會先到它們這一層請求。如果這一層記憶體池不夠用時,會按照下面的順序一層一層的往上申請記憶體:

mcache -> mcentral-> mheap -> 作業系統

mspan&&arenas

先來看看 mspan 這個基礎結構體。首先,當 Go 在程式初始化的時候,會將申請到的虛擬記憶體劃分為以下三個部分:
記憶體管理

arenas 也就是動態分配的堆區,它將分配到的記憶體以 8k 為一頁進行管理。
在這裡插入圖片描述
然而 “頁” 這個單位還是太細了,因此再抽象出 mspan 這一層來管理,mspan 表示一組連續的頁面。

mspan

mspan 記錄了這組連續頁面的起止地址、頁數量、以及型別規格。

關於 mspan 的型別規格有 67 種,每一種都被定義了一個固定大小,當有物件需要分配記憶體時,就會挑選合適規格的 mspan 分配給物件。

class  1      2      3      4      5      6  ···   63      64      65      66

bytes  8      16     32     48     64     80 ···  24576   27264   28672   32768

例如給大小為 30B 的物件分配記憶體時,就會選擇型別規格 class 為 3,也就是大小為 32B 的 mspan 分配。這種分配方法跟 linux 用於記憶體分配的夥伴演算法差不多,能有效地減少記憶體碎片。

剛剛提到虛擬記憶體劃分還有個 bitmap 區域,bitmap 主要用來標記 arena 區域中哪些地址儲存了物件, GC 掃描資訊以及物件指標資訊。

總體上來講,spans 和 bitmap 區域可以看做是 arenas 區域的後設資料資訊,輔助記憶體管理。

mheap && mcentral

mheap 在 Go 裡是一個全域性物件,用來管理大於 32K 物件的記憶體分配。

mcentral 維護了各個規格的 mspan。當它的下級 mcache 記憶體不足時,則會到 mcentral 這裡來申請 mspan。

由於 mcentral 有各個規格型別的 mspan,因此當有不同規格的分配請求時,並不會產生併發競爭的問題。只有當同型別規格的 mspan 併發請求分配時,才會有加鎖操作。

mheap

mcache

mcache 是提供給 P 的本地記憶體池。(關於 GPM 模型可以看這篇 golang 重要知識:golang 排程),由於每次只會有一個 Goroutine 在 P 上執行。所以分配記憶體是不需要競爭的。

mcache 上還有微型分配器,當要分配更小元素:即 <= 16B 時,會在一個 8byte 的 mspan 上分配多個的物件,這樣就能更好的利用記憶體空間。

mcache

三、總體流程

記憶體分配流程

  • 當要分配大於 32K 的物件時,從 mheap 分配。
  • 當要分配的物件小於等於 32K 大於 16B 時,從 P 上的 mcache 分配,如果 mcache 沒有記憶體,則從 mcentral 獲取,如果 mcentral 也沒有,則向 mheap 申請,如果 mheap 也沒有,則從作業系統申請記憶體。
  • 當要分配的物件小於等於 16B 時,從 mcache 上的微型分配器上分配。
本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章