Go Runtime(四):内存分配器
本文源码基线为 Go 1.26.2、Linux/amd64。文中的源码链接固定到 go1.26.2 tag;对象大小阈值、size class 表和局部快路径可能随版本变化。 Go 内存分配器要同时满足三类目标:小对象分配足够快,多 P 并发时减少共享锁竞争,GC 又能由任意堆地址快速定位对象边界和标记元数据。它采用按对象规格分级、按 P 缓存、按页统一管理的结构。 对象是否进入这里首先由编译器逃逸分析决定;本文从已经确定需要堆分配的对象开始,追踪 mallocgc 到 mcache、mcentral、mheap 和页分配器。 先确认对象是否真的进入堆 new(T) 不等于堆分配,返回局部变量地址也不必机械地等于堆分配;编译器根据值是否可能在当前栈帧结束后继续可达、地址是否暴露给未知调用等条件做逃逸分析,随后内联和标量替换还可能消除分配。 GOpackage alloc type Node struct { next *Node val int } //go:noinline func makeNode(v int) *Node { return &Node{val: v} } BASHgo test -gcflags='all=-m=2' ./... go test -run='^$' -bench=. -benchmem ./... 第一条解释编译器决定,第二条测量最终二进制的每次操作分配。只看源码语法或只看逃逸日志都不足以判断生产路径成本。 分配器的层级 Go 将小对象归入 size class,再按 spanClass 管理 span。spanClass 同时编码 size class 和对象是否为 noscan。 ...