Go 语言,内存的分配原理
Go 中实现的内存分配器,简单的说就是维护了一大块全局内存,每个线程(Go 中的 P)维护一小块的私有内存,当私有内存不足时再向全局申请。内存分配与 GC(垃圾回收)有密切关系。
概念
为了方便自主管理内存,做法便是先向系统申请一块内存,然后将内存切割成小块,通过一定的内存分配算法管理内存。以 64 位系统为例,Golang 程序启动时会向系统申请的内存如下图所示:
预申请的内存划分为 spans、bitmap、arena 三部分
arena 即所谓的堆区,应用中需要的内存从这里分配,spans 和 bitmaps 是用来管理 arena 的。
arena :大小为 512 G,为了方便管理把 arena 区域划分成一个个的 page,每个 page 为 8KB,一共有 512GB/8KB 个页;
spans :区域存放 span 的指针,每个指针对应一个 page,所以 span 区域的大小为(512GB/8KB)*指针大小 8byte =512M
bitmap :区域大小也是通过 arena 计算出来,不过主要用于 GC。
span
span 是用于管理 arena 页的关键数据结构,每个 span 中包含 1 个或多个连续页,为了满足小对象分配,span 中的一页会划分更小的粒度,而对于大对象比如超过页大小,则通过多页实现。
内存分配过程
针对待分配对象的大小不同有不同的分配逻辑:
申请一块较大的虚拟内存空间,用于内存分配及管理
当空间不足时,向系统申请一块较大的内存,如 100KB 或者 1MB
申请到的内存块按特定的 size,被分割成多种小块内存(go:_NumSizeClasses = 67),并用链表管理。创建对象时,按照对象大小,从空闲链表中查找到最适合的内存块。
销毁对象时,将对应的内存块返还空闲链表中以复用。
空闲内存达到最大值时,返还操作系统。
管理组件
go 将内存分为三个层级,协程私有内存,全局内存,整体内存叶管理,也因此有这三个层级的内存管理工具。
mspan: mspan 并不直接拥有内存空间,它负责管理起始地址为 startAddr、级别(预分配页的个数)为 sizeclass 的连续地址空间。
mcache: Per-P 私有 cache,用于实现无锁的 object 分配,每个 mcache 有大小为 67 的 mspan 数组,存储不同级别大小的 mspan。
mcentral: 全局内存,为各个 cache 提供按大小划分好的 mspan,mcentral 有个关键方法 cacheSpan(),它是整个分配的核心算法
mheap 是真实拥有虚拟地址的结构,page 管理,内存不足时向系统申请。
总结
Golang 程序启动时申请一大块内存,并划分成 spans、bitmap、arena 区域
aren a 区域按页划分成一个个小块
span 管理一个或多个页
mcentral 管理多个 span 供线程申请使用
mcache 作为线程私有资源,资源来源于 mcentral
评论