基本策略
- 每次从操作系统申请一个大块内存(比如1MB),以此减少系统调用
- 将申请到的大块内存按照特定大小预先切分成小块,构成链表
- 为对象分配内存时,只需从大小合适的链表提取一个小块即可
- 回收对象内存时,将该小块内存重新归还到原链表,以便复用
- 如果闲置内存过多,则尝试归还部分内存给操作系统,降低整体开销
tips:内存分配只管理内存块,并不关心对象状态。且它不会主动回收内存,垃圾回收器在完成清理操作后,触发内存分配器的回收操作。
内存块
管理的内存块分为两种
- span:由多个地址连续的页(page)组成的大块内存(面向内部管理)
- object: 将span按特定大小切分成多个小块,每个小块可以存储一个对象(面向对象分配)
- 按页数区分不同大小的span,需要时按页数作为索引查找。没有合适的就取更大的进行裁剪,多余部分放回管理数组。并会将地址相邻的span合并,减少碎片
_PageShift = 13
...
pageShift = _PageShift
_PageSize = 1 << _PageShift // 8kb
- 对于存储对象的object,按8字节倍数分为n种。如24字节的可以存储17-24字节的对象。初始化时,会构建存储大小和规格的对应关系,切分
_NumSizeClasses = 68
...
var class_to_size = [_NumSizeClasses]uint16{0, 8, 16...}
var class_to_allocnpages = [_NumSizeClasses]uint8{0, 1, 1...}
var size_to_class8 = [smallSizeMax/smallSizeDiv + 1]uint8{0, 1, 2, 3...}
var size_to_class128 = [(_MaxSmallSize-smallSizeMax)/largeSizeDiv + 1]uint8{32, 33, 34, 35...}
...
// 小对象尺寸上限
_MaxSmallSize = 32768
结构体
type mspan struct {
next *mspan // next span in list, or nil if none
prev *mspan // previous span in list, or nil if none
list *mSpanList // For debugging. TODO: Remove.
startAddr uintptr // address of first byte of span aka s.base()
npages uintptr // number of pages in span
manualFreeList gclinkptr // list of free objects in mSpanManual spans
allocBits *gcBits
gcmarkBits *gcBits
// sweep generation:
// if sweepgen == h->sweepgen - 2, the span needs sweeping
// if sweepgen == h->sweepgen - 1, the span is currently being swept
// if sweepgen == h->sweepgen, the span is swept and ready to use
// if sweepgen == h->sweepgen + 1, the span was cached before sweep began and is still cached, and needs sweeping
// if sweepgen == h->sweepgen + 3, the span was swept and then cached and is still cached
// h->sweepgen is incremented by 2 after every GC
sweepgen uint32
...
}
管理组件
malloc.go:base on tcmalloc,在性能与内存利用率之间做出平衡
- cache:每个运行期工作线程都会绑定一个cache,用于无锁object分配
- central:为所有cache提供切分好的后备span资源
- heap:管理限制span,需要时向操作系统申请新内存
结构体
type mheap struct {
allspans []*mspan // all spans out there
// 每个central对应一种sizeclass
central [numSpanClasses]struct {
mcentral mcentral
pad [cpu.CacheLinePadSize - unsafe.Sizeof(mcentral{})%cpu.CacheLinePadSize]byte
}
}
type mcentral struct {
spanclass spanClass // 规格
// 由于每次gc循环sweepgen增加2,所以清理过的spans为partial[sweepgen/2%2]
// 以下都[2],其中一个上清理过的,一个是没清理过的
partial [2]spanSet // list of spans with a free object
full [2]spanSet // list of spans with no free objects
}
type mcache struct {
...
alloc [numSpanClasses]*mspan // spans to allocate from, indexed by spanClass
}
分配流程
- 计算待分配对象的规格(size class)
- 从cache.alloc数组中找到对应的规格相同的span
- 从span.manualFreeList提取可用的object
- 如果manualFreeList为空,则从central获取新span
- 如果central没有span则从heap中拿
central分配span
- 先看已扫描的partial列表中是否有span,有则pop
- 100次循环,看未扫描的partial列表,如果sweepgen=sg-2,说明需要被扫描。此时将它进行扫描设置为sg-1,然后用它
- 100次循环,未扫描full列表,同上。通过span的allocCache找到下一个freeIndex,返回。如果满了就把它丢进需要清扫的队列,继续循环。
- 实在没有了,就只能去heap里申请一个。