携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第22天,点击查看活动详情 >>
golang memory management

Page Heap(mheap):
go语言存储动态数据的位置,也是GC最主要的区域。resident set被分成一个个大小为8kb的页,并且由一个全局的mheap对象管理。
若有大于32KB的对象需要被存储,则直接从mheap分配空间,系统会对此请求加锁,保证任何时间只有一个P可以被分配空间。
mheap将管理的页分成不同的结构:
mspan:双向链表,保存着起始页的地址、存储块所分成的页的大小和存储块中页面的数量,go将存储页分成8b-32kb 67个不同的大小。
每个跨度都存在两个,一个是用于存储对象的指针(scan),一个是用存储对象本身(noscan),这样方便在GC期间不需要遍历 noscan 来寻找存活的对象。
mcentral:大小与上文提及的跨度对应,且每个mcentral包含两个List。
- empty:双向链表,存储着非自由的对象和span,当一个span被释放,则将其移动到non-empyt链表中。
- non-empty:存储着自由对象的span的双向链表,当收到一个来自 mcentral 的请求时,其将一个span从non-empty移动到empty。
当mcentral没有剩余的span时,将从mheap请求新的存储页。
arena:堆内存在分配的虚拟存储中根据需要增加或减少。当需要分配更多的存储时,mheap将从虚拟存储中拉取大小为64MB的块,其被称为arena,并将页面在其上映射为span。
mcache:其被用来提供给P存储一些小的对象(size ≤ 32kb)。对于所有的大小类型的mspan mcache都包含scan和noscan。Goroutines可以在没有任何锁的情况下从mcache获取内存,因为一个P一次只能有一个G。mcache当其需要的时候将从mcentral获取span。
(mcache其实是逻辑上的存储结构,而其映射到的mcentral才是真正的物理上的存储结构)
Stack与Goroutines对应,保存Goroutines的相关信息。
Memory Allocation
正如前文所述 Golang 的存储结构相当复杂,所以其页采用了一种复杂的方法进行垃圾回收。Golang 使用一种 thread-local 缓存来加快小对象的分配并且维护 scan/noscan 来加速GC。这种结构以及进程缓存在很大程度上避免了碎片化,使得GC期间不需要压缩。
Go根据对象的大小来决定对象的分配过程,它分为三类:
Tiny(大小<16B):小于16字节的对象是使用mcache中的小的跨度来存储(例如8B、16B)的。多个微小的对象是在一个16B的块上完成的。具体分配过程参考下面链接:
Small(大小 16B~32KB):大小在16B到32KB节之间的对象在G运行的P的mcache上的相应大小类(mspan)上分配。具体分配过程参考下面链接:
在Tiny分配和Small分配中,如果mspan的列表是空的,分配器将从mheap获得一个页面以用于mspan。如果mheap是空的,或者没有足够大的页,那么它将从操作系统中分配一组新的页(至少1MB)。
Large(大小>32KB):在mheap的相应大小类上直接分配大小大于32kb的对象。如果mheap是空的,或者没有足够大的页,那么它将从操作系统中分配一组新的页(至少1MB)。