Go内存分配语雀笔记整理
- Golang内存模型设计理念思考
- 核心代码阅读
- mspan
- mcache
- mcentral中心缓存
- mheap
- 分配过程
Golang内存模型设计理念思考
golang内存分配基于TCmalloc模型,它核心在于:空间换时间,一次缓存,多次复用;
为何要有这种缓存机制?如果golang每次分配内存都向操作系统申请,需要切换到内核态发起系统调用,(权级切换时的开销是很大的)代价太高;所以一次缓存,多次复用(类似GMP的设计思想尽可能不切到内核态)
堆mheap这种数据结构就是一次缓存得到的那部分。 在操作系统视角中,mheap就是用户进程缓存的内存,在golang视角中,mheap就是内存的源头
mheap作为进程中的内存来源,全局唯一,具有全局锁,如果mheap直接分配,那么存取性能是非常可怕的,为了提高分配效率,就要实现锁的粒度更细、甚至无锁,goalng借鉴操作系统的多级缓存模型(L1,L2,L3,RAM,磁盘),设计了一种3层缓存模型:mheap、 mcentral、 mchace(GMP中的P相当于一个CPU,而mache就是P本地私有的内存,它冗余了各种等级的mspan,P本地私有不涉及并发行为,所以不需要加锁);
核心代码阅读
mspan
mspan是最小的内存管理单元,它跟golang设定的对象等级制度紧密关联;mspan结构体的核心字段:相同等级的mspan构成一条双向链表,所以mspan有前后指针; mspan管理整数倍的连续地址的页,所以有管理的内存起始地址,以及连续的页数; mspan的等级意味着它管理的块的大小,所以有一个标志等级的变量(1~67、以及0级处理大对象,等级用unit8来表示,高7位标识等级,最低位标识noscan信息,noscan用于标记当前mspan的内存需不需要在GC的时候进一步扫描,如果对象不包含指针,说明对象没有引用其他对象,那么垃圾回收的时候就不需要进一步扫描了。这样可以提高扫描效率); 分配内存时分配的其实是对应的槽位,所以有一个位图用来记录槽位,比如要分配31B的obj,那么就会向上到等级为32的mspan中根据它的位图找空闲的槽位(这是内部碎片的来源)。这个位图bitmap在后续的垃圾回收中也会使用到。 它的快速查找空闲槽用到了CTZ64算法
mspan详解:既然相同size class的mspan节点聚集构成一条双向链表,所以mspan节点有next,pre指针,另外一个mspan管理的内存大小从startaddr开始,往后npages个page。当要分配obj的时候,会去alloccache记录的bitmap中去找为1的obj。 alloccache记录的bitmap就是记录obj的槽位
源码中核心的点: next、pre指针; startaddr; npages;alloccache(bitmap);spanclass(等级);freeindex(记录bitmap在某位之前的obj都被占用过了,加速找bitmap中Obj为1的槽位)
另外spanclass 是一个uint8 整数,高7位标识1~67+隐藏0级(处理更大对象) 共68个等级,最低位标识noscan信息。noscan标志是用来标记垃圾回收的时候需不需要扫描的标志位。
如果对象不包含指针,说明对象没有引用其他对象,那么垃圾回收的时候就不需要进一步扫描了。这样可以提高扫描效率
mcache
mcache是P的本地私有内存,在单个P内部是没有并发的,所以没有锁; 核心字段: alloc, mcache冗余了一些mspan,具体来说的话是两种,一种noscan类型的无需重复扫描,一种scan类型的。 另外mcache结构体中还有微型对象分配器(<16B)tinyalloc。
mcentral中心缓存
mcentral是相同等级mspan构成的两条链表,一条有空间,一条满空间,每个mcentral有一把锁,但是锁的粒度相比mheap更细;所以核心字段的话就是两条链表,一把锁,一个等级;
mheap
进程内全局唯一,访问加全局锁(因此对mheap的存取性能是非常可怕的),对于go上层来说mheap是操作系统虚拟内存的抽象,是go进程内部的内存之源,由于向操作系统申请内存这个操作很重,所以mheap一般一次申请比较大(64M)
mheap以8KB的页为最小内存单元,负责将连续的页组装成mspan,使用位图bitmap记录所有页的使用情况,为1说明页已经被mspan组装(不是被使用,组装了不一定使用了)(组装的时候找连续空闲页时为了加速,使用到了空闲页基数树索引辅助) 用heapArena聚合页记录页跟mspan的映射关系(GC的时候根据对象地址找到页是很简单的,但是mspan的记录对象使用情况的bitmap也需要更新,所以需要heapArena记录页跟mspan的映射关系,用来更新mspan的bitmap)。
分配过程
tiny微对象(<16B )的话,走mcache的微型分配器;
(<32KB的对象)优先从mcache中找对应等级mspan中的空槽(无锁),如果mcache兜不住就向上去mcentral;
根据等级去mcentral中的有空闲链表中找到mspan填充mcache(有锁),如果兜不住,继续向上去mheap;
mheap组装mspan填充到mcentral,然后从mspan中取内存(有锁),如果还兜不住,说明进程中没有足够的内存了,此时mheap会向操作系统申请内存(以heapArena为单位64MB);
0级别的大对象(>32KB),直接到mheap中