Golang内存管理初探

Golang内存分配原理

Golang是使用的自实现的内存分配算法,更类似于tcmalloc,维护一块全局的大内存,每个线程(golang中的Processor)各自维护一块小的私有内存,当私有内存不足时,再从全局进行申请。

1、申请全局内存

为了方便做自主的内存管理,首先需要向系统申请一块全局内存,然后将内存切割成小块,按照一定得方式来管理。
当程序启动时,申请内存,并将其分成三个部分:
span bitmap arena
512MB 16G 512G

其中arena就是堆区,应用中需要的所有内存都从这里分配,而span和bitmap则是为了管理arena而存在的

arena总共512G(包含虚拟内存),为了方便管理,把这个区域划分为一个个的Page,每个page的大小是8KB(Windows系统页大小的两倍)

span区域存放span的指针,每个指针对应一个或多个Page,所以span区的大小是 512G/8kb * 8byte = 512MB

bitmap区域大小也是通过arena计算出来,主要是用来GC

2、span

span是用来管理page的关键结构,每个span中包含1个或者多个连续的page,通常为了满足小对象的分配,span中的一页会划分更小的粒度,而对于超过一页大小的大对象,则会使用多页来实现

2.1 class

class还是值得说一下的,class共有67种(66+1),分别代表从8字节到32KB大小的不同对象,超过32K大小的对象用特殊的class来表示,ID为0,每个class只包含一个对象
class是span可以handle的数据的类型,是个虚拟概念,不同ID表示span可以处理的数据大小、类型不同

2.2 cache

span是用来管理内存的基本单位,mcentral是用来管理span的数据结构;各个线程需要内存时就从mcentral管理的span中申请,为了避免多线程的时候申请内存时需要不断加锁解锁,所以Golang为每一个线程分配了Span的缓存,这个缓存就是cache。

type mcache struct {
    alloc [67*2]*mspan // 按class分组的mspan列表
}

alloc就是span的指针数组,每个class类型都有两组(*2)span列表,第一组列表中所表示的对象中包含了指针,第二组列表的对象中不包含指针,所以GC扫描的时候不需要考虑这一部分

mcache在初始化的时候,是不包含任何span的,在使用过程中会动态的从central中获取并缓存下来,而根据使用的情况,每种class的span个数也不相同

2.3 central

cache作为线程的私有资源,为线程服务。而central则是全局资源,每当有线程内存不足,便向central申请,待内存释放后,再归还给central

每个mcentral只管理特定class的span,而所有的mcentral的集合在mheap中

3.内存分配过程

16Byte(class 1和2)做为界限,以下不含指针的,tiny分配,含指针的,正常分配
16b到32K之间(66种class),正常分配
32K以上,大对象分配

tiny分配和大对象分配属于内存管理的优化部分

以申请size n的内存为例:
1、获取当前线程的私有mcache
2、根据size计算出合适的class的ID
3、从mcache的alloc[class]链表中查询可用的span
4、如果mcache没有可用的span则从mcentral中申请一个,加入到线程的mcache中
5、如果mcentral也没有可用,则从mheap中申请一个新的span加入到mcentral
6、从该span中获取空闲对象地址并返回

4.总结

1、内存管理方式类似于tcmalloc,开局申请大块内存,分为三个部分,span 512M /bitmap 16G/arena 512G
2、arena被划分成8k一页大小的一个个页,span来管理一个或者多个页
3、内存申请路径 mcache -> mcentral -> mheap

本文链接:

https://omen.ltd/archives/12/
1 + 9 =
快来做第一个评论的人吧~