1.内存分配的基本策略
<1>. 每次从操作系统申请⼀⼤块内存(⽐如 1MB),以减少系统调⽤。
<2>. 将申请到的⼤块内存按照特定⼤⼩预先切分成⼩块,构成链表。
<3>. 为对象分配内存时,只需从⼤⼩合适的链表提取⼀个⼩块即可。
<4>. 回收对象内存时,将该⼩块内存重新归还到原链表,以便复⽤。
<5>. 如闲置内存过多,则尝试归还部分内存给操作系统,降低整体开销。
2.基本概念
<1>.内存分配器只管理内存块,并不关⼼对象状态。且不会主动回收内存,由垃圾回收器在完成清理
操作后,触发内存分配器回收操作。
<2>.Golang 的内存分配采⽤了 tcmalloc 的成熟架构
<3>.分配器将其管理的内存块分为两种:
• span: 由多个地址连续的页(page)组成的⼤块内存。
• object: 将 span 按特定⼤⼩切分成多个⼩块,每个⼩块可存储⼀个对象。
照其⽤途,span ⾯向内部管理,object ⾯向对象分配。
<4>.Golang的内存分配器由三种组件组成。
• cache: 每个运⾏期⼯作线程都会绑定⼀个 cache,⽤于⽆锁 object 分配。(分配object)
• central: 为所有 cache 提供切分好的后备 span 资源。(把span切分为object)
• heap: 管理闲置 span,需要时向操作系统申请新内存。(申请/偿还span给操作系统)
<5>.一页内存8KB;以8字节为倍数,总共67种不同大小内存;超过32KB被认为是大对象
<6>.为⼯作线程私有且不被共享的 cache 是实现⾼性能⽆锁分配的核⼼,⽽ central 的作⽤是
在多个 cache 间提⾼ object 利⽤率,避免内存浪费。
<7>.假如 cache1 获取⼀个 span 后,仅使⽤了⼀部分 object,那么剩余空间就可能会被浪费。⽽回收操作将该 span 交还给 central 后,cache1已不再持有该 span,那么该span就会重新分配
<8>.将 span 归还给 heap,可被其他需求获取,重新切分,就可以在不同规格 object 需求间平衡。
3.分配流程:
<1>. 计算待分配对象对应规格(size class)。
<2>. 从 cache.alloc 数组找到规格相同的 span。
<3>. 从 span.freelist 链表提取可⽤ object。
<4>. 如 span.freelist 为空,从 central 获取新 span。
<5>. 如 central.nonempty 为空,从 heap.free/freelarge 获取,并切分成 object 链表。
<6>. 如 heap 没有⼤⼩合适的闲置 span,向操作系统申请新内存块。
4.释放流程:
<1>. 将标记为可回收 object 交还给所属 span.freelist。
<2>. 该 span 被放回 central,可供任意 cache 重新获取使⽤。
<3>. 如 span 已收回全部 object,则将其交还给 heap,以便重新切分复⽤。
<4>. 定期扫描 heap ⾥长时间闲置的 span,释放其占⽤内存。
<5>.⼤对象,直接从 heap 分配和回收。
5.内存对象
//span 对象
type mspan struct {
next *mspan //双向链表
prev *mspan //
start pageID // 起始序号 = (address >> _PageShift)
npages uintptr // 页数
freelist gclinkptr // 待分配的object链表
}
//三组件
//heap组件
type mheap struct {
free [_MaxMHeapList]mspan //页数在127以内的闲置span链表数组
freelarge mspan //页数大于127(>=1MB)的大span链表数组
central [_NumSizeClasses]struct// 每个central对应一种sizeclass
{
mcentral mcentral
}
}
//mcentral组件
type mcentral struct {
sizeclass int32//规格
nonempty mspan//链表:尚有空闲object的span
empty mspan//链表:没有空闲 object,或已被cache取走的span
}
//cache组件
type mcache struct {
alloc [_NumSizeClasses]*mspan //以sizeclass为索引管理多个用于分配的span
}
6.手工理解图