Go内存分配原理详解
Go内存分配器采用多级缓存结构,优化分配性能。
三层结构概览
Go
┌─────────────────────────────────────┐
│ mheap │
│ (全局堆,从OS申请内存arena) │
└─────────────────────────────────────┘
↑ 向上提供
│
┌─────────────────────────────────────┐
│ mcentral │
│ (中央缓存,67种size class) │
└─────────────────────────────────────┘
↑ 向上提供
│
┌─────────────────────────────────────┐
│ mcache │
│ (本地缓存,每P独享,无锁) │
└─────────────────────────────────────┘
↑
P(处理器)
mcache本地缓存
结构
Go
type mcache struct {
alloc [67]*mspan // 67种大小类别的span
// ...
}
// 每个P独享一个mcache
type p struct {
mcache *mcache
}
无锁分配
Go
// 分配流程
func mallocgc(size uintptr) unsafe.Pointer {
// 1. 确定size class
sizeclass := sizeToClass(size)
// 2. 从当前P的mcache获取
c := getcurrentp().mcache
span := c.alloc[sizeclass]
// 3. 从span分配对象(无锁)
obj := span.alloc()
return obj
}
mcache绑定P,同一时刻只有一个G使用,无需锁。
mcentral中央缓存
结构
Go
type mcentral struct {
sizeclass int8
partial mSpanList // 有空闲对象的span
full mSpanList // 无空闲对象的span
}
// 全局67个mcentral
var mcentral [67]mcentral
职责
Go
// mcache空了向mcentral申请
func (c *mcentral) cacheSpan() *mspan {
// 需要加锁(但调用频率低)
lock(&c.lock)
// 从partial链表获取
span := c.partial.pop()
unlock(&c.lock)
return span
}
mheap全局堆
结构
Go
type mheap struct {
arenas []arena // 内存区域
// ...
}
type arena struct {
spans [pagesPerArena]*mspan
}
// arena通常是64MB(Linux)
向OS申请内存
Go
func (h *mheap) allocSpan(npages uintptr) *mspan {
// 使用mmap系统调用
addr := sysAlloc(npages * pageSize)
// 组织成span
span := &mspan{startAddr: addr, npages: npages}
return span
}
size class大小类别
67种预定义大小
Go
// 8B到32KB,共67种
sizeclasses = [...]uint16{
8, 16, 24, 32, 48, 64, 80, 96, 112, 128,
144, 160, 176, 192, 208, 224, 240, 256,
// ... 直到32KB
}
分配策略
Go
// 分配15字节
// → 使用size class 2(16字节)
// 浪费1字节,但减少碎片
// 分配超过32KB
// → 直接从mheap分配,不经过mcentral
span内存块
结构
text
type mspan struct {
startAddr uintptr // 起始地址
npages uintptr // 页数(每页8KB)
sizeclass uint8 // 大小类别
nelems uint16 // 对象数量
allocCount uint16 // 已分配数量
freeindex uintptr // 下一个空闲索引
}
示例
text
// size class 1(8字节)
// span大小:1页 = 8KB
// 对象数量:8KB / 8B = 1024个
// span内部布局:
// [obj8][obj8][obj8]...[obj8]
// 共1024个8字节槽位
分配流程总结
text
┌─────────────────────────────────────┐
│ 分配请求 │
└─────────────────────────────────────┘
↓
┌─────────────────────────────────────┐
│ 检查大小是否超过32KB │
└─────────────────────────────────────┘
↓ 小于 ↓ 大于
┌──────────────┐ ┌──────────────┐
│ 确定sizeclass│ │ 直接从mheap │
└──────────────┘ │ 分配大块 │
↓ └──────────────┘
┌─────────────────────────────────────┐
│ 从P的mcache获取对应span │
└─────────────────────────────────────┘
↓ 有空间 ↓ 无空间
┌──────────────┐ ┌──────────────┐
│ 直接分配 │ │ 从mcentral │
│ (无锁) │ │ 申请新span │
└──────────────┘ └──────────────┘
↓ mcentral空
┌──────────────┐
│ 从mheap申请 │
└──────────────┘
↓ mheap空
┌──────────────┐
│ 向OS申请内存 │
│ (mmap) │
└──────────────┐
各层职责对比
| 层级 | 位置 | 锁 | 频率 | 说明 |
|---|---|---|---|---|
| mcache | 每P | 无 | 极高 | 大部分分配在此完成 |
| mcentral | 全局67个 | 有 | 低 | mcache空时补充 |
| mheap | 全局 | 有 | 极低 | mcentral空时补充 |
| OS | 系统 | - | 最少 | mheap空时申请 |
要点总结
- 三层结构:mcache→mcentral→mheap
- mcache绑定P,无锁分配
- 67种size class,8B-32KB
- 超过32KB直接从mheap分配
- span是基本分配单元
- 分配优先级:mcache→mcentral→mheap→OS
- mcache无锁是性能关键
📝 发现内容有误?点击此处直接编辑