Go語言——內存管理
參考:
問題
- 內存碎片:避免內存碎片,提高內存利用率。
- 多線程:穩定性,效率問題。
內存分配

- arena即為所謂的堆區,應用中需要的內存從這里分配, 大小為512G,為了方便管理把arena區域划分成一個個的page,每個page為8KB,一共有512GB/8KB個頁
- spans區域存放span的指針,每個指針對應一個page,所以span區域的大小為
(512GB/8KB) * 指針大小8byte = 512M
- bitmap區域大小也是通過arena計算出來
512GB / (指針大小(8 byte) * 8 / 2) = 16G
,用於表示arena區域中哪些地址保存了對象, 並且對象中哪些地址包含了指針,主要用於GC。
分配細節
- object size > 32K,則使用 mheap 直接分配。
- object size < 16 byte,不包含指針使用 mcache 的小對象分配器 tiny 直接分配;包含指針分配策略與[16 B, 32 K]類似。
- object size >= 16 byte && size <=32K byte 時,先使用 mcache 中對應的 size class 分配。
- 如果 mcache 對應的 size class 的 span 已經沒有可用的塊,則向 mcentral 請求。
- 如果 mcentral 也沒有可用的塊,則向 mheap 申請,並切分。
- 如果 mheap 也沒有合適的 span,則向操作系統申請。
span
可以看出span是一個非常重要的數據結構,每個span包含若干個連續的page。
小對象分配會在span page中划分更小的粒度;大對象通過多頁實現。
size class
go1.10\src\runtime\sizeclasses.go
// class bytes/obj bytes/span objects tail waste max waste
// 1 8 8192 1024 0 87.50%
// 2 16 8192 512 0 43.75%
// 3 32 8192 256 0 46.88%
// 4 48 8192 170 32 31.52%
// 5 64 8192 128 0 23.44%
// 6 80 8192 102 32 19.07%
// 7 96 8192 85 32 15.95%
// 8 112 8192 73 16 13.56%
// 9 128 8192 64 0 11.72%
// 10 144 8192 56 128 11.82%
// ...
// 65 28672 57344 2 0 4.91%
// 66 32768 32768 1 0 12.50%
上表中每列含義如下:
- class: class ID,每個span結構中都有一個class ID, 表示該span可處理的對象類型
- bytes/obj:該class代表對象的字節數
- bytes/span:每個span占用堆的字節數,也即頁數*頁大小
- objects: 每個span可分配的對象個數,也即(bytes/spans)/(bytes/obj)
- tail bytes: 每個span產生的內存碎片,也即(bytes/spans)%(bytes/obj)
上表可見最大的對象是32K大小,超過32K大小的由特殊的class表示,該class ID為0,每個class只包含一個對象。所以上面只有列出了1-66。
有點像裝箱算法,按照規格分配,減少內存碎片。
struct
span是內存管理的基本單位,每個span用來管子特定的size class對象,根據size class,span將若干個頁分成塊進行管理。
go1.10\src\runtime\mheap.go
type mspan struct {
next *mspan // next span in list, or nil if none
prev *mspan // previous span in list, or nil if none
startAddr uintptr // address of first byte of span aka s.base()
npages uintptr // number of pages in span
nelems uintptr // number of object in the span.
allocBits *gcBits
gcmarkBits *gcBits
allocCount uint16 // number of allocated objects
spanclass spanClass // size class and noscan (uint8)
elemsize uintptr // computed from sizeclass or from npages
}

以size class 10為例,npages=1,nelems=56,spanclass=10,elemsize=144;startAddr指arena區位置;next和prev指spans區,span鏈表;allocBits是一個bitmap,標記分配塊分配情況,這個設計我也用過,之前用redis bitmap實現了IPAM。
cache
從上面我們知道go通過span來分配內存,那在哪里用span?通過之前的學習Go語言——goroutine並發模型,我們知道每個P都有mcache,通過mcache管理每個G需要的內存。
go1.10\src\runtime\mcache.go
type mcache struct {
tiny uintptr
tinyoffset uintptr
alloc [numSpanClasses]*mspan // spans to allocate from, indexed by spanClass
}
numSpanClasses = _NumSizeClasses << 1
_NumSizeClasses = 67
alloc是span數組,長度是67 << 1,說明每種size class有2組元素。第一組span對象中包含了指針,叫做scan,表示需要gc scan;第二組沒有指針,叫做noscan。提高gc scan性能。
mcache初始沒有span,G先從central動態申請span,並緩存在cache。
central
go1.10\src\runtime\mcentral.go
type mcentral struct {
lock mutex
spanclass spanClass
nonempty mSpanList // list of spans with a free object, ie a nonempty free list
empty mSpanList // list of spans with no free objects (or cached in an mcache)
// nmalloc is the cumulative count of objects allocated from
// this mcentral, assuming all spans in mcaches are
// fully-allocated. Written atomically, read under STW.
nmalloc uint64
}
- lock: 多個G並發從central申請span,所以需要lock,保證一致性
- spanclass : 每個mcentral管理着一組有相同size class的span列表
- nonempty: 指還有內存可用的span列表
- empty: 指沒有內存可用的span列表
- nmalloc: 指累計分配的對象個數
線程從central獲取span步驟如下:
- 加鎖
- 從nonempty列表獲取一個可用span,並將其從鏈表中刪除
- 將取出的span放入empty鏈表
- 將span返回給線程
- 解鎖
- 線程將該span緩存進cache
線程將span歸還步驟如下:
- 加鎖
- 將span從empty列表刪除
- 將span加入nonempty列表
- 解鎖
heap
central只管理特定的size class span,所以必然有一個更上層的數據結構,管理所有的sizeclass central,這就是heap。
go1.10\src\runtime\mheap.go
type mheap struct {
lock mutex
spans []*mspan
// Malloc stats.
largealloc uint64 // bytes allocated for large objects
nlargealloc uint64 // number of large object allocations
largefree uint64 // bytes freed for large objects (>maxsmallsize)
nlargefree uint64 // number of frees for large objects (>maxsmallsize)
// range of addresses we might see in the heap
bitmap uintptr // Points to one byte past the end of the bitmap
bitmap_mapped uintptr
arena_start uintptr
arena_used uintptr // Set with setArenaUsed.
arena_alloc uintptr
arena_end uintptr
arena_reserved bool
central [numSpanClasses]struct {
mcentral mcentral
pad [sys.CacheLineSize - unsafe.Sizeof(mcentral{})%sys.CacheLineSize]byte
}
}
- spans:映射span -> page
- large:大對象,>32K
- bitmap: gc
- arena: arena區相關信息,pages,堆區
- central:通過size class管理span,每種size class對應兩個central
