内存管理单元
内存管理单元有如下
-
page: x64下大小为8k。go与OS内存申请与释放都是以page为单位
-
span: 多个连续page组成,是内存管理的基本单元
-
mcache: 每个P所有的cache,包含多个空闲内存块链表,不同的链表上的内存块大小可能是不相同的
-
mcentral: 是所有线程共享的缓存,需要加锁访问。按 Span class 对 Span 分类,串联成链表,当 mcache 的某个级别 Span 的内存被分配光时,它会向 mcentral 申请 1 个当前级别的 Span
-
heapArena: heapArena 是由 mheap 管理的更小的内存块。整个堆内存被划分为多个 heapArena,每个 heapArena 通常是 64MB(在 64 位系统上)的大小。每个 heapArena 都有自己独立的管理数据结构,用于跟踪和管理该 heapArena 中的内存分配情况
-
mheap: 堆内存的抽象,把从 OS 申请出的内存页组织成 Span,并保存起来
mheap将span组织为树结构,按照page数量进行排序
- free: 保存的 span 是空闲并且非垃圾回收的 span
- scav: 保存的是空闲并且已经垃圾回收的 span
此外,内存管理还存在大小概念,用于更好地分配合适的内存块
-
objectSize: 申请内存的实际大小
-
size class: 表示 size 的级别,相当于把 size 归类到一定大小的区间段,比如
size[1,8]
属于size class 1
,size(8,16]
属于size class 2
-
span class: 与size class对应,表示特定大小的span类别,并且还有有指针和无指针两个版本(无指针不需要gc扫描)
共有0-133级别,每个级别都有nonempty和empty span链表。
- nonempty 保证所有 span 都至少有 1 个空闲的对象空间。这些 span 是 mcache 释放 span 时加入到该链表的
- empty 所有的 span 都不确定里面是否有空闲的对象空间。当一个 span 交给 mcache 的时候,就会加入到 empty 链表
分配过程
Go按照不同对象大小有不同的分配逻辑
微对象(0, 16B)
mcache中的微分配器进行分配的
在mcache结构体中有如下字段
-
tiny uintptr: 指向当前微小块(tiny block)的起始位置,或者如果当前没有微小块则为 nil.
用于跟踪分配给微小对象的内存块
-
tinyoffset uintptr: 表示当前微小块的偏移量,用于指示在当前微小块中已使用的内存位置
-
tinyAllocs uintptr:记录由拥有这个 mcache 的 P(Processor)执行的微小分配次数
微分配器将多个微小块分配请求合并到同一个内存块中,只有当内存块中的所有对象都需要被回收时,整片内存才可能被回收。
微分配器管理的对象不可以是指针类型,管理多个对象的内存块大小可以通过变量 maxTinySize
调整,在默认情况下,内存块的大小为 16 字节
小对象[16B, 32KB]
根据span class映射表寻找span,分配对象空间
- 计算对象所需内存大小size
- 映射size到size class
- 根据size class和对象是否包含指针计算出span class
- 获取该span class指向的span
在具体申请span上
- 会首先向mcache中申请,不行则向mcentral申请
- mcentral会先从non empty搜索满足条件的span,然后再从empty进行寻找
- 在mcentral也找不到的情况下,就向mheap申请span
- mheap会优先从free搜索可用的span,然后再从scav寻找
- 最后还是找不到,就只能向OS申请内存了
大对象(32KB以上)
直接runtime.largeAlloc
分配大块内存
Ref
- https://medium.com/safetycultureengineering/an-overview-of-memory-management-in-go-9a72ec7c76a8
- https://draveness.me/golang/docs/part3-runtime/ch07-memory/golang-memory-allocator/
- https://xiaoming.net.cn/2020/12/16/Go%20%E5%86%85%E5%AD%98%E7%AE%A1%E7%90%86%E4%B8%8E%E5%9E%83%E5%9C%BE%E5%9B%9E%E6%94%B6/