1.內存分配的基本策略
<1>. 每次從操作系統申請⼀⼤塊內存(⽐如 1MB),以減少系統調⽤。
<2>. 將申請到的⼤塊內存按照特定⼤⼩預先切分成⼩塊,構成鏈表。
<3>. 爲對象分配內存時,只需從⼤⼩合適的鏈表提取⼀個⼩塊即可。
<4>. 回收對象內存時,將該⼩塊內存重新歸還到原鏈表,以便復⽤。
<5>. 如閒置內存過多,則嘗試歸還部分內存給操作系統,降低整體開銷。
2.基本概念
<1>.內存分配器只管理內存塊,並不關⼼對象狀態。且不會主動回收內存,由垃圾回收器在完成清理
操作後,觸發內存分配器回收操作。
<2>.Golang 的內存分配採⽤了 tcmalloc 的成熟架構
<3>.分配器將其管理的內存塊分爲兩種:
• span: 由多個地址連續的頁(page)組成的⼤塊內存。
• object: 將 span 按特定⼤⼩切分成多個⼩塊,每個⼩塊可存儲⼀個對象。
照其⽤途,span ⾯向內部管理,object ⾯向對象分配。
<4>.Golang的內存分配器由三種組件組成。
• cache: 每個運⾏期⼯作線程都會綁定⼀個 cache,⽤於⽆鎖 object 分配。(分配object)
• central: 爲所有 cache 提供切分好的後備 span 資源。(把span切分爲object)
• heap: 管理閒置 span,需要時向操作系統申請新內存。(申請/償還span給操作系統)
<5>.一頁內存8KB;以8字節爲倍數,總共67種不同大小內存;超過32KB被認爲是大對象
<6>.爲⼯作線程私有且不被共享的 cache 是實現⾼性能⽆鎖分配的核⼼,⽽ central 的作⽤是
在多個 cache 間提⾼ object 利⽤率,避免內存浪費。
<7>.假如 cache1 獲取⼀個 span 後,僅使⽤了⼀部分 object,那麼剩餘空間就可能會被浪費。⽽回收操作將該 span 交還給 central 後,cache1已不再持有該 span,那麼該span就會重新分配
<8>.將 span 歸還給 heap,可被其他需求獲取,重新切分,就可以在不同規格 object 需求間平衡。
3.分配流程:
<1>. 計算待分配對象對應規格(size class)。
<2>. 從 cache.alloc 數組找到規格相同的 span。
<3>. 從 span.freelist 鏈表提取可⽤ object。
<4>. 如 span.freelist 爲空,從 central 獲取新 span。
<5>. 如 central.nonempty 爲空,從 heap.free/freelarge 獲取,並切分成 object 鏈表。
<6>. 如 heap 沒有⼤⼩合適的閒置 span,向操作系統申請新內存塊。
4.釋放流程:
<1>. 將標記爲可回收 object 交還給所屬 span.freelist。
<2>. 該 span 被放回 central,可供任意 cache 重新獲取使⽤。
<3>. 如 span 已收回全部 object,則將其交還給 heap,以便重新切分復⽤。
<4>. 定期掃描 heap ⾥長時間閒置的 span,釋放其佔⽤內存。
<5>.⼤對象,直接從 heap 分配和回收。
5.內存對象
//span 對象
type mspan struct {
next *mspan //雙向鏈表
prev *mspan //
start pageID // 起始序號 = (address >> _PageShift)
npages uintptr // 頁數
freelist gclinkptr // 待分配的object鏈表
}
//三組件
//heap組件
type mheap struct {
free [_MaxMHeapList]mspan //頁數在127以內的閒置span鏈表數組
freelarge mspan //頁數大於127(>=1MB)的大span鏈表數組
central [_NumSizeClasses]struct// 每個central對應一種sizeclass
{
mcentral mcentral
}
}
//mcentral組件
type mcentral struct {
sizeclass int32//規格
nonempty mspan//鏈表:尚有空閒object的span
empty mspan//鏈表:沒有空閒 object,或已被cache取走的span
}
//cache組件
type mcache struct {
alloc [_NumSizeClasses]*mspan //以sizeclass爲索引管理多個用於分配的span
}
6.手工理解圖