02. 核心概念与术语表

02. 核心概念与术语表

Go 内存管理 Wiki / 第一部分:全景概览

Go 内存管理涉及的所有核心概念,用最通俗的语言讲清楚


本文是参考手册,不需要从头读到尾。后续 Wiki 中遇到不认识的术语,随时回来查阅即可。


1. 内存分配术语

堆(Heap) heap

程序运行时动态分配的内存区域。Go 中所有 newmake 创建的对象,以及逃逸到堆上的变量,都存放在堆上。堆是 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 都会沿用这套配色。

相关推荐
January12071 小时前
Cassandra CQL 基本命令详解
java
Escalating_xu1 小时前
【Linux】线程概念与控制:从地址空间、LWP 到 pthread_create、join、detach 与 NPTL
java·linux·redis
Lam Tang1 小时前
APS 系列文章 09
java·代理模式
evans在进步1 小时前
LeetCode 198:打家劫舍——Java 动态规划详解
java·leetcode·动态规划
2601_963870222 小时前
【计算机毕业设计】基于Spring Boot的考研信息交流平台的设计与实现
java·spring boot·后端
大模型丫丫2 小时前
Spring Boot 入门指南:从零开始构建你的第一个应用
java·spring boot·后端
拾光Ծ2 小时前
Linux五种I/O模型与fcntl非阻塞控制
java·linux·服务器·i/o模型
半旧5182 小时前
【Java Web02】WEB服务器内部实现技术揭秘
java·服务器·前端
gis开发之家2 小时前
日志怎么打才专业?Spring Boot 4 日志体系与 Logback 配置(生产级实战)
java·spring boot·后端·logback