這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
記憶體配置和記憶體回收有關,這裡我們可以先看一下記憶體配置。
記憶體回收比較複雜,後面講。
一開始雨痕大大說了幾個基本策略:
- 每次從作業系統申請一大塊記憶體(比如1MB),以減少系統調用。
- 將申請到的大塊記憶體按預定大小預先切分成小塊,構成鏈表。
- 為對象分配記憶體時,只需從大小合適的鏈表中提取一個小塊即可。
- 回收對象記憶體時,將該小塊記憶體重新歸還到原鏈表,以便複用。
- 如果閑置記憶體過多,則嘗試歸還部分記憶體給作業系統,降低記憶體開銷。
裡面提到了兩個概念
span: 就是1提到的大塊記憶體被切成小塊的鏈表。
object: 就是上面說的小塊。
這裡span雖然是按照小塊的規格進行了分級,但實際上採取了比較靈活的策略,可能會在必要條件下把較大規格span的鏈表的一部分借給較小規格的span,實際合并時也會嘗試合并相鄰的span。
����golang直接採用tcmalloc的成熟架構。
cache central heap
這裡做了三級緩衝。
cache是每個golang裡面的P搞一個,提供一個無鎖分配。
然後central根據sizeclass的大小,把所有中等的object分成若干等級,然後在這裡做其中一級緩衝,減少計算量。。
heap則是最後一道緩衝,在這裡發起回收和mmap申請。
然後雨痕給了圖說明的記憶體分布以及初始化的過程。
還順帶普及了一下內聯最佳化。某些簡單的函數塊可能會被最佳化掉,裡面該從堆分配的,卻被弄到了棧分配。。。
cache部分的邏輯:
從代碼中可以看出tiny&large object做了特定的處理。tiny是使用classsize=2的span,然后里面使用盡量共用空間,希望一個object能夠多複用幾次。用cache.tinyoffset, cache.tiny進行控制。
大對象則直接使用堆分配。。
同時還介紹了一個
systemstack(func() {
s = largeAlloc(size, uint32(flags))
})這個使用系統棧來啟動並執行分配函數,保證全域同步。
mcentral部分的邏輯:
這個傢伙內部也有兩個小緩衝。
這還略屌的。
仔細看代碼可以知道,mcentral的細節策略還是很多的,比如先考慮nonempty再考慮empty,使用empty時要先來個msweep嘗試一下。。。是在不行了才去找heap。。。
mheap部分的邏輯:
和預料的基本一致,作為最後一級的緩衝。會先考慮自己的free,不行了,才考慮向系統申請。裡面的freelarge是個鏈表在free沒有的時候,會用freelarge,順序遍曆鏈表找出最合適(在大於目標size裡面最小)的freelarge。
#
#
回收
上來就一個綜述:回收不會盯著object而是整個span。
大概就是遍曆span將不可達object合并到freelist,如果已經回收所有object,則將這塊記憶體還給heap。 細節部分,這裡它會考慮同右側的span進行合并。
釋放
這裡的入口有sysmon發起。每5分鐘都來一次。
大概就是把heap裡面的free和freelarge的span list給拿出來幹掉。
細節方面這裡用了madvise,用來在某些情境下提升效能。
其他
大概就是還有四塊系統的部分也需要記憶體回收。
它們自己內部也弄了個二級緩衝。
這裡就比較簡單了,是fix size的,沒有span、object、classsize,第一層級用了而是一個chunk,還有複用的list。。。第二級就是使用chunk。
還有個什麼record的函數。
大概就是為了提高span的便利效率而做了一個數群組類型的h_spans, 然後在特定條件下引發擴容,每次擴容 A*3/2 +1。