内存分配
arena
这块区域最大,明显就是用来存放我们最终的对象,里面分成了一个个8K大小的房间,每个房间我们称为page。(这里虽然写了它是512G,但是你心里要有B数,你电脑根本没这么大的内存,其实操作系统只是给了你地址而已)同时几个page组合在一起的大房间又叫做mspan(这个是golang中内存管理的基本单元)
bitmap
然后我们再来看第二大的bitmap,它是用来表示arena中存放的对象的一些信息,包括这个对象GC标志,还有标识这个对象是否包含指针。你肯定就好奇,干嘛要有这个呢?这其实也很好理解,golang在进行垃圾回收的时候是根据引用的可达性分析来确定一个对象是否可以被回收,同时采用的是三色标记法进行标记对象,所以这里需要有bitmap来保存这些信息。(具体如果不清楚垃圾回收的细节可以去看看我之前写的有关垃圾回收的部分)
spans
最后是spans,这里保存了mspan的指针,这个也好理解,为了方便管理那一个个大房间嘛
mspan是堆上内存管理的基本单元,由一连串的页构成(8kb)。golang根据内存大小将mspan分成了67个等级,每个mspan被切分成了很多小的对象,用于不对尺度 的对象分配。
每个线程有一个独立的堆内存mcache,在mcache上申请内存不需要加锁,每个mache有67种尺度内存单元,每个尺度的内存又由2个mspan构成,分别存储指针类型和非指针类型。当程序在堆上创建对象时,分配器会根据对象的大小(小于32kb)和类型(是否为指针)从67*2种mspan中分配内存。
如果mache内存不足,会从所有线程共享的缓存mcentral中申请内存。一共有67*2种mcentral, mcache中空间不足的mspan会从对应的mcentral申请内存,申请内存时需要加锁,但是加锁的概率变成了1/(67*2),申请效率很高。为了进一步提升内存分配效率,每一个mcentral由2个链表构成,一个存储已经分配给mcache的mspan,一个存放未被占用或者已经被mcache释放的mspan。
当mcentral上mspan不足时或者对象大于32kb时会从mheap上申请内存,全局仅有一个mheap,也需要加锁访问。mspan中有2棵二叉排序树(根据mpan中的page数量):free,scav,free存放的是空闲非垃圾回收的mspan,scav存放的是空闲已经垃圾回收的mspan。内存分配时优先从free
中搜索可用的span,如果没有找到,会从scav
中搜索可用的span,如果还没有找到,它会向OS申请内存,再重新搜索2棵树,必然能找到span。从OS申请的也会被保存到span中,然后加入free树中。