02. 核心概念与术语表
Go 内存管理 Wiki / 第一部分:全景概览
Go 内存管理涉及的所有核心概念,用最通俗的语言讲清楚
本文是参考手册,不需要从头读到尾。后续 Wiki 中遇到不认识的术语,随时回来查阅即可。
1. 内存分配术语
堆(Heap) heap
程序运行时动态分配的内存区域。Go 中所有 new、make 创建的对象,以及逃逸到堆上的变量,都存放在堆上。堆是 GC 管理的主要区域。
栈(Stack) stack
每个 goroutine 拥有自己的栈,用于存放局部变量和函数调用信息。栈上的变量在函数返回时自动释放,不需要 GC 回收。Go 编译器会做逃逸分析,尽量把变量分配在栈上以减少 GC 压力。
逃逸分析 escape analysis
编译器在编译阶段分析变量的生命周期,决定它是分配在栈上还是堆上。如果一个变量的地址被返回、存入全局变量、或被闭包捕获,它就会"逃逸"到堆上。用 go build -gcflags="-m" 可以查看逃逸分析结果。
P / M / G Processor / Machine / Goroutine
Go 调度器的三个核心概念:G = goroutine(用户级协程);M = Machine(操作系统线程);P = Processor(逻辑处理器,数量 = GOMAXPROCS)。每个 P 拥有一个 mcache ,所以同一时刻最多有 GOMAXPROCS 个 goroutine 在无锁分配内存。
mcache memory cache
每个 P 独有的内存缓存。alloc [numSpanClasses]*mspan 数组按 spanClass 索引,分配小对象时直接从对应 span 的空闲位图中查找,完全无锁 。定义在 mcache.go:20。
mcentral memory central
每种 spanClass 对应一个 mcentral,管理该大小类的所有 span。当 mcache 的 span 用完时,从 mcentral 获取新的 span。mcentral 使用双缓冲 设计(partial2 / full2),GC 周期切换时角色互换。定义在 mcentral.go:22。
mheap memory heap
全局唯一的堆管理器,管理所有堆内存。包含页分配器(pageAlloc)、中心缓存数组(central[])、Arena 映射表等。全局唯一,操作需要加全局锁。定义在 mheap.go:64。
fixalloc fixed-size allocator
定长对象分配器,用于分配分配器自身的元数据 (如 mspan、mcache 结构体),解决"分配器的元数据也需要分配"的循环依赖问题。定义在 mfixalloc.go:31。
2. Span 相关术语
页(Page) page
Go 内存管理的最小单位,大小固定为 8192 字节(8KB) 。源码中 pageSize = 1 << 13 = 8192。所有堆内存都以页为粒度管理。
mspan memory span
一段连续的页,是内存分配的基本单元。一个 mspan 包含若干页(1~10 页用于小对象,更多页用于大对象),被切分成等大小的对象槽位。每个 mspan 有分配位图(allocBits)和标记位图(gcmarkBits)。定义在 mheap.go:422。
size class size class
Go 把小对象按大小分成 67 个级别 (从 8 字节到 32768 字节)。分配时把请求大小向上取整到最近的 size class。例如申请 20 字节会被分配 24 字节(class 3)。这样减少了碎片,也加速了分配。完整表见 sizeclasses.go。
spanClass span class
在 size class 基础上增加了 scan/noscan 维度,编码为 spanClass = (sizeclass << 1) | noscan。高 7 位是 size class 编号,最低位 1 表示 noscan(不含指针,GC 不扫描),0 表示 scan。总共约 136 个 spanClass。定义在 mheap.go:567-578。

图 2-1:spanClass 的 8 位编码(高 7 位 = size class,最低位 = noscan 标志)
arena arena
Go 把堆地址空间划分为固定大小的区域,称为 arena。64 位系统上每个 arena = 64MB ,32 位/Windows = 4MB。每个 arena 有一个 heapArena 元数据结构,存储页到 span 的映射。定义在 mheap.go:268。
chunk chunk
页分配器内部的管理单位,一个 chunk = 512 页 = 4MB。页分配器的位图按 chunk 组织,每个 chunk 有独立的分配位图和 scavenged 位图。
allocBits / gcmarkBits allocation bits / GC mark bits
每个 mspan 有两套位图:allocBits 记录哪些对象槽位已分配(1=已分配,0=空闲);gcmarkBits 记录哪些对象被 GC 标记为存活。GC 清扫时,gcmarkBits 变成新的 allocBits(存活对象保留,垃圾对象标记为空闲)。
sweepgen sweep generation
清扫世代号,用于追踪 span 是否已被清扫。每次 GC 后 h.sweepgen += 2。span 的 sweepgen 相对于 h.sweepgen 有 5 种状态,决定它是否需要清扫、是否可用。

图 2-2:mspan 的三种状态及转换关系
3. 垃圾回收术语
GC Garbage Collection
垃圾回收,自动回收不再使用的内存。Go 使用并发标记-清扫算法,大部分时间和用户代码同时运行。
三色标记法 tri-color marking
GC 标记阶段的核心算法。白色 = 未访问(可能垃圾);灰色 = 已标记但子对象未扫描(在工作队列中);黑色 = 已扫描完毕(确定存活)。GC 从灰色队列取出对象扫描,直到没有灰色对象,剩下的白色对象就是垃圾。
根(Root) GC root
GC 扫描的起点,包括:① 全局变量(data/BSS 段);② 所有 goroutine 的栈;③ 有 finalizer 的对象。从根出发能"到达"的对象就是存活的。
写屏障 write barrier
GC 标记期间,每次修改指针时 runtime 自动执行的额外操作。Go 使用混合写屏障 (Dijkstra 插入屏障 + Yuasa 删除屏障),把被覆盖的旧指针和新写入的指针都标灰,保证并发标记不会漏掉对象。声明在 mbarrier.go:30。
STW Stop-The-World
停止所有 goroutine 的执行。Go 的 GC 只在两个时刻 STW:① Sweep Termination(启动 GC 前的准备);② Mark Termination(标记完成的验证)。两处 STW 通常都在微秒级。
gcphase GC phase
GC 的当前阶段,有三个值:_GCoff(GC 未运行)、_GCmark(并发标记中)、_GCmarktermination(标记终止中)。定义在 mgc.go:250-254。
heapLive heap live bytes
当前堆中存活对象的估算字节数。每次分配会增加 heapLive,GC 清扫后会减少。当 heapLive 达到触发阈值时,GC 被触发。存储在 gcControllerState 中。
heapGoal heap goal
下一次 GC 的堆目标大小。计算公式:goal = heapMarked * (1 + GOGC/100)。当 GOGC=100(默认)时,目标是上次 GC 后存活堆大小的 2 倍。
Mutator Assist mutator assist
分配器辅助标记机制。如果用户代码分配内存太快、GC 标记跟不上,分配器会"帮"GC 做一些标记工作(调 gcAssistAlloc),防止堆增长过快。这是一种背压机制。
Sweep sweep
清扫阶段,把 GC 标记为垃圾的对象回收。具体做法是:遍历每个 span,把 gcmarkBits 变成新的 allocBits(标记的对象保留,未标记的变为空闲)。清扫是并发的,在后台 goroutine 和分配时按需进行。
Scavenge scavenge
把空闲的物理内存归还给操作系统。Sweep 回收的内存只是从"垃圾"变成了"空闲 span",仍被 Go 持有。Scavenge 调用 madvise(MADV_FREE)(Linux)或 VirtualFree(MEM_DECOMMIT)(Windows)真正释放物理页。后台 scavenger 以 ~1% CPU 持续运行。
Pacing GC pacing
GC 步调控制,决定何时触发 GC 以及分配器辅助的比率。目标是在堆增长到 heapGoal 之前完成标记,避免堆增长失控。核心逻辑在 gcControllerState,定义在 mgcpacer.go:92。
wbBuf write barrier buffer
每个 P 的写屏障缓冲区,容量 512 个指针。写屏障不是每次都同步执行 shade 操作,而是先写入缓冲区,满了再批量处理,减少开销。定义在 mwbbuf.go:42。
gcWork GC work
每个 P 的 GC 工作队列,存储待扫描的灰色对象指针。使用双缓冲 设计(wbuf1/wbuf2),减少对全局工作队列的锁竞争。定义在 mgcwork.go:83。
4. 配置与环境变量
GOGC GC percentage
控制 GC 触发频率的环境变量,默认值 100 。含义是:堆增长到上次 GC 后存活大小的 (1 + GOGC/100) 倍时触发 GC。GOGC=100 翻倍触发;GOGC=200 3 倍触发;GOGC=50 1.5 倍触发;GOGC=off 禁用 GC。

图 2-3:GOGC 参数如何控制 GC 触发时机
GOMEMLIMIT memory limit
Go 1.19 引入的软内存上限,默认值 math.MaxInt64 (无限制)。设置后,当 Go 持有的内存接近 GOMEMLIMIT 时,GC 会更积极地运行。与 GOGC 协同工作:取两者中更严格的约束。例如 GOMEMLIMIT=512MiB 让 Go 尽量不超过 512MB。
GODEBUG=gctrace=1 GC trace
开启 GC 日志,每次 GC 输出一行信息,包含:GC 次数、STW 时间、标记时间、堆大小等。是分析 GC 行为最常用的工具。输出格式示例:
gc 1 @0.045s 1%: 0.013+0.36+0.022 ms clock, 0.10+0.17/0.30/0.65+0.18 ms cpu, 4->4->2 MB, 5 MB goal, 0 MB stacks, 0 MB globals, 8 P
runtime.GC() manual GC trigger
手动触发一次完整的 GC。通常只在测试和基准测试中使用,生产环境不建议调用。
runtime.ReadMemStats() memory statistics
获取内存统计信息,返回 runtime.MemStats 结构体,包含堆分配量、GC 次数、STW 时间等。注意:调用时会 STW。
5. 完整术语对照表
按字母/拼音排序,方便快速查找:
| 术语 | 英文 | 一句话解释 | 源码位置 |
|---|---|---|---|
| allocBits | allocation bits | span 的分配位图,1=已分配,0=空闲 | mheap.go:468 |
| allocCache | allocation cache | allocBits 的 64 位补码缓存,用 CTZ 指令加速查找 | mheap.go:466 |
| arena | arena | 64MB(64位)的地址空间区域,有独立元数据 | mheap.go:268 |
| chunk | chunk | 页分配器管理单位,512 页 = 4MB | mpagealloc.go |
| fixalloc | fixed-size allocator | 定长对象分配器,分配分配器自身的元数据 | mfixalloc.go:31 |
| gcDrain | GC drain | 标记核心循环,从工作队列取出灰色对象扫描 | mgcmark.go:1244 |
| gcmarkBits | GC mark bits | span 的 GC 标记位图,1=已标记存活 | mheap.go:468 |
| gcphase | GC phase | GC 阶段:_GCoff / _GCmark / _GCmarktermination | mgc.go:250 |
| gcWork | GC work | Per-P 的 GC 工作队列,双缓冲设计 | mgcwork.go:83 |
| GOGC | GC percentage | GC 触发频率,默认 100(堆翻倍时触发) | 环境变量 |
| GOMEMLIMIT | memory limit | 软内存上限,Go 1.19 引入 | 环境变量 |
| greyobject | grey object | 把白色对象标灰(标记位设 1 + 入队) | mgcmark.go:1644 |
| heapGoal | heap goal | 下次 GC 的堆目标 = heapMarked × (1 + GOGC/100) | mgcpacer.go |
| heapLive | heap live bytes | 当前堆存活字节数的估算值 | mgcpacer.go |
| heapMarked | heap marked | 上次 GC 标记的存活字节数 | mgcpacer.go |
| mallocgc | malloc GC | Go 内存分配总入口函数 | malloc.go:1067 |
| mcache | memory cache | Per-P 无锁缓存,大部分分配在此完成 | mcache.go:20 |
| mcentral | memory central | Per-sizeClass 中心缓存,双缓冲设计 | mcentral.go:22 |
| mheap | memory heap | 全局堆管理器,管理所有堆内存 | mheap.go:64 |
| mspan | memory span | 连续页组成的基本分配单元 | mheap.go:422 |
| Mutator Assist | mutator assist | 分配器辅助 GC 标记,防止堆增长过快 | mgcmark.go:499 |
| nextFreeFast | next free fast | 用 CTZ 指令 O(1) 查找空闲槽位 | malloc.go:969 |
| noscan | no scan | 对象不含指针,GC 不需要扫描 | mheap.go:573 |
| pageAlloc | page allocator | 基数树页分配器,管理连续页的分配 | mpagealloc.go:186 |
| Pacing | GC pacing | GC 步调控制,决定触发时机和辅助比率 | mgcpacer.go:92 |
| refill | refill | mcache 从 mcentral 获取新 span 的操作 | mcache.go:160 |
| scan | scan | 对象含指针,GC 需要扫描其内容 | mheap.go:573 |
| Scavenge | scavenge | 把空闲物理内存归还给操作系统 | mgcscavenge.go |
| size class | size class | 67 个大小级别,从 8B 到 32KB | sizeclasses.go |
| spanClass | span class | sizeclass + noscan 标志,约 136 种 | mheap.go:567 |
| STW | Stop-The-World | 停止所有 goroutine,通常微秒级 | mgc.go |
| Sweep | sweep | 清扫垃圾对象,回收 span 空间 | mgcsweep.go |
| sweepgen | sweep generation | 清扫世代号,追踪 span 是否已清扫 | mheap.go:73 |
| 三色标记 | tri-color marking | 白(未访问)灰(待扫描)黑(已完成) | mgcmark.go |
| 写屏障 | write barrier | GC 期间修改指针时自动执行的 shade 操作 | mbarrier.go:30 |
| wbBuf | write barrier buffer | Per-P 写屏障缓冲区,512 个指针批量处理 | mwbbuf.go:42 |
配色说明 :本系列 Wiki 使用统一的配色方案贯穿全文------蓝色 = 分配/mcache、琥珀色 = GC/mcentral、紫色 = mheap/元数据、青色 = OS/scavenge、红色 = STW/垃圾、绿色 = 存活/成功。后续每篇 Wiki 都会沿用这套配色。