Skip to content
All writing Part 08 of 11 · 從 19 個 goroutine 到 exit 137
Engineering · 2 min read

分配器內部:size class、fragmentation、scavenger

size class 是速度和碎片的平衡。Go 分配器怎麼把 page 切成 slot、碎片從哪來、為什麼 pprof 掉了 RSS 沒跟著掉。

size class 是速度和碎片的平衡,分配快,但位置不一定剛好。

四層配置階梯

new(obj) mcache (per-P, lock-free) miss mcentral (per-size-class, locked) miss mheap (global, locked) miss kernel (mmap syscall)
  • mcache(per-P):每個 P 持有各 size class 的 span cache。大多數配置在這裡完成,無鎖、無 syscall(GMP 排程器與 netpoller)。
  • Size classes(8B, 16B, 24B, 32B⋯):10-byte 物件放 16B slot。同尺寸在同 span → 空出的 slot 立刻能被同 class 重用。
  • Bitmap free-slot search:每個 span 用 bitmap 追蹤 slot 狀態。找空位 = 找最低 set bit,O(1) per word。

碎片:內部和外部

internal fragmentation obj 10B 6B waste 16B slot allocator rounds up external fragmentation page (4 KB) 3 live slots 卡住整個 page
  • Internal:size-class 向上取整(17B → 24B)。Go 的 67 個 size class 控制浪費 < 12%。struct field 排列也影響:{int8, int64, int8} = 24B,{int64, int8, int8} = 16B。
  • External:散落的活物件卡住整個 span,空間存在但無法歸還。Go 用 non-move GC,低 CPU 成本,但不壓縮。

Scavenger 與 madvise

  • 背景 scavenger 定期透過 madvise 歸還 idle pages。MADV_DONTNEED(Go ≥1.16 預設)= 立即歸還,RSS 掉,但重用要 page fault。MADV_FREE(Go <1.16)= 懶回收,RSS 看起來黏住。
  • Go 1.16 改回 MADV_DONTNEED 的原因:太多「RSS 怎麼不掉」的 false alarm。

在這個 OOM 裡的角色

這次 OOM 不是分配器碎片造成的,是瀏覽器記憶體。但理解分配器能解釋為什麼「pprof 的 inuse 掉了,RSS 沒掉」,因為分配器拿了記憶體不一定馬上還(記憶體三層模型)。

分配器是 kernel 和 heap 之間的中間人。不理解它,RSS 和 pprof 之間的差距就永遠對不起來。

參考來源:

Related: 參見記憶體三層模型OOM 診斷決策樹GMP 排程器與 netpoller,或回到系列總覽

Tags #go #concurrency #reliability
// connect

Be brave | Be wise | Be grateful

21 BreakinCode

// elsewhere
LinkedInMedium (lang: en)Youtube
wh:~$William Hung· © 2026 Taipei · GMT+8 · Available for collaboration