这是我参与「第五届青训营」笔记创作活动的第十二天
Go堆内存分配
堆上的所有对象都会通过mallocgc分配指定大小的内存空间,runtine系列的new、make函数都利用它
func mallocgc(size uintptr, typ *_type, needzero bool) unsafe.Pointer {
mp := acquirem()
mp.mallocing = 1
c := gomcache()
var x unsafe.Pointer
noscan := typ == nil || typ.ptrdata == 0
if size <= maxSmallSize {
if noscan && size < maxTinySize {
// 微对象分配
} else {
// 小对象分配
}
} else {
// 大对象分配
}
publicationBarrier()
mp.mallocing = 0
releasem(mp)
return x
}
从mallocgc函数可以看出它会根据对象的大小选择不同的分配逻辑
- 微对象
(0, 16B)— 先使用微型分配器,再依次尝试线程缓存、中心缓存和堆分配内存; - 小对象
[16B, 32KB]— 依次尝试使用线程缓存、中心缓存和堆分配内存; - 大对象
(32KB, +∞)— 直接在堆上分配内存;
接下来会依次梳理内存分配的核心流程
大对象
大于32KB的内存分配请求额外处理,这很好理解。因为最大的内存规格才32KB。所以会直接根据需要的页面数,分配一个新的跨度类为0的span。
微对象
而对于小于16B的内存分配,也不直接匹配预置内存规格。主要是为了减少浪费,如果需要分配16次的1B内存,每次分配时匹配预置的内存规格最小8B,那么每次都会浪费7B。而tiny allocator能够将几个小块的内存分配请求合并,所以16次1B的内存分配请求,可以合并到1个16B的内存块中。诸如此类可以提高内存使用率
那tiny allocator从哪里分配内存呢,上次我们提到过,每个P的mchahe这里,有专门用于tiny allocator的内存mcahe.tiny。 这是一个16B大小的内存单元,mcache.tinyOffset记录这段内存已经用到了哪里。如果tiny allocator要分配size大小的内存空间,而mcahce中的tinyOffset经过对齐后。还够分配size大小的内存,就在tiny内存中直接分配。如果剩余空间不够了,就从当前P的mcache中,找到对应的mspan,重新拿一个16字节大小的内存块来用。如果本地缓存中相应的mspan也没有空间了,就会从mcentral中拿一个新的mspan过来用,分配完以后,如果新拿来的内存块剩余空间,比旧内存块的剩余空间还大,那就用新的内存块把旧的内存块替换掉,这就是tiny allcoator的大致工作流程
小对象
至于最后一种,直接通过本地mcache与全局mcentral配合合作,找到匹配规格的mspan即可