整体介绍
Golang 的内存分配机制是 Go 运行时系统的重要组成部分,设计目标是:高效、可扩展、低延迟。它在运行时使用了一套三层分配器架构:mcache、mcentral 和 mheap,配合**垃圾回收器(GC)**一起工作。
一、整体架构(重点)
Go 的内存分配分为两个大类:
| 类型 | 特点 | 分配路径 |
|---|---|---|
| 小对象(<=32KB) | 使用 内存池 + 对象缓存 的方式 | mcache → mcentral → mheap |
| 大对象(>32KB) | 直接从堆上分配 | 直接使用 mheap |
其中主要的组件如下:
| 组件 | 作用 |
|---|---|
mcache | 每个 P 绑定的本地缓存,最快 |
mcentral | 跨线程共享缓存,中等速度 |
mheap | 操作操作系统内存,最慢 |
二、重要概念
P、M、G 模型
- P(Processor):调度器,持有
mcache。 - M(Machine):真正的执行线程。
- G(Goroutine):用户代码。
每个 P 对应一个 mcache,用于缓存小对象,加快分配速度。
三、分配流程(重点)
小对象(<=32KB)分配流程:
// 示例:make([]int, 10)- 调用
runtime.newobject()或runtime.mallocgc() - 在当前
P的mcache中找对应大小 class 的 span(object 块集合) - 如果找到了可用对象,直接分配,更新 bitmap
- 如果
mcache中没有空闲对象:- 从
mcentral获取新的 span
- 从
- 如果
mcentral也没有可用 span:- 从
mheap获取新的 span,并返回给mcentral
- 从
mheap无法满足时,从操作系统分配新的内存页(通过sysAlloc)
newobject() 本质就是调用了 mallocgc(),带上了一些参数(类型信息、是否需要 GC 跟踪等)
重点机制:
- 大小分级(size class):将对象按大小分成约 67 个级别(8B 到 32KB)
- Span:是一组相同大小对象的内存块集合
大对象(>32KB)分配流程:
- 直接向
mheap请求span - 跳过
mcache和mcentral - 通常直接通过
sysAlloc从操作系统申请内存页(类似mmap)
四、垃圾回收(GC)与内存回收
Go 使用 三色标记清除算法(concurrent mark & sweep):
| 阶段 | 工作内容 |
|---|---|
| STW 开始 | 停止世界,准备标记 |
| 并发标记 | 标记活动对象(灰 → 黑) |
| 并发清除 | 回收未标记对象(白) |
| STW 清理 | 停止世界,清理并恢复运行 |
GC 如何影响内存分配?
- GC 回收对象后,会将对应的 span 标记为可重用。
mcentral会将清理后的 span 返回给mcache。- 整个过程尽量最小化 STW 时间,保证低延迟。
五、相关数据结构
| 名称 | 描述 |
|---|---|
mcache | 每个 P 的本地缓存 |
mcentral | 跨 P 的全局缓存 |
mheap | 管理 Span 的堆 |
span | 固定大小的内存块集合 |
bitmap | GC 用于追踪对象存活状态 |
arena | Go 向操作系统申请的大内存区块 |
🔍 六、调试和观察工具
| 工具 | 用法 |
|---|---|
GODEBUG=gctrace=1 | 打印 GC 日志 |
pprof | 性能分析,包括内存分析 |
runtime.ReadMemStats | 获取当前内存使用情况 |
总结(重点回顾)
核心流程:
mcache(P 本地) → mcentral(全局) → mheap(系统堆)对象分级:
- 小对象(≤ 32KB)使用 span 缓存快速分配
- 大对象(> 32KB)走系统分配路径
性能优化重点:
- 减少 GC 触发频率
- 优化对象复用和生命周期
- 合理控制内存占用
Last updated on