前言
Go中栈内存也是从堆内存中申请的,那堆内存是什么结构呢?堆内存如何分配内存呢?
操作系统内存结构
操作系统中,每个进程都有一个独立的巨大的虚拟内存,这些内存实际映射到物理内存上。而当物理内存空间不够时,就会触发oom,杀掉一个进程来释放内存。
图示如下:
而Go运行时也是一个进程,也会拥有一片巨大的虚拟内存,那Go是怎么使用的呢?
heapArena
Go把虚拟内存视为堆内存,并进行分块,每一块单元大小为64MB。每个单元对应一个heapArena结构体来描述单元信息。
那虚拟内存为256TB,每个单元大小为64MB,理论上能有2^20个单元和heapArena。另外,所有的heapArena都储存在mheap结构体中。图示如下:
当然一般只申请几百个heapArena,远远小于理论值。
heapArena如何分配内存
一个heapArena对应单元大小为64MB,分配内存时结构如下:
那每次分配内存给一个对象时,该怎么安排它在内存中的位置呢?
线性分配
简单方案是线性分配,每个Object进入都挨着最后一个Object的位置放下。
如果有GC清理了Object,则将空闲碎片空间加入链表中。
下一个进入的Object大小如果小于链表的空闲空间,则优先放入链表位置,否则仍线性分配。图示如下:
新进入的Object如果碎片空间足够,优先放置。
新进入的Object因空间碎片过小放不下,就顺序放置。
但是,如果所有新进入的Object都比空间碎片大,那链表中的空闲块就一直存在,无法利用而浪费空间。故该方案不采用。
分级分配
分级分配会预先将heapArena划分为不同大小的块,如下图:
第一行有八个同样大小的块,第二行有四个,以此类推。什么用意呢?这样每个进来的Object会根据自己的大小来选择刚好能放下自己的空间。如下图:
虽然划分区中仍有部分空间碎片,但这很小。这样一来,每个Object都放置到最适合自己的空间上,极大的避免了空间浪费。
mspan
基于分级分配的策略,Go将每一级视为最小空间单位mspan。如下图:
上图有四级则有四种类别的mspan,那Go种总共有多少种mspan呢?如下图:
答案是一共有68种,上图隐藏了class0类别。解释下上图意思,如第一种,每个单元有8bytes,一共有1024个单元,那么该span总共有8192个字节,即8KB。max waste表示最大浪费比例,如果只用到1字节,对于有8字节的单元格来说,浪费了87.5%的空间。
展示下mspan部分结构:
type mspan struct {
next *mspan
prev *mspan
spanclass spanClass
elemsize uintptr
...
}
- next, prev字段使mspan变为一个双向链表
- spanclass表示mspan的类别,共有68种
- elemsize表示一个单元格大小
而对于heapArena来说,并不会把67种mspan都加入进去,而是根据需要加入某些类别的mspan。
以上了解了heapArena的内部结构,那问题来了:
假设我想要class为3的mspan,该如何查找?
整体内存结构:
从mheap来看,有很多的heapArena,每个heapArena各自有不同类别mspan。
想寻找特定的mspan,就得循环所有的heapArena,逐一判断了。但这样太耗时间了,于是Go用了索引中心mcentral,来快速定位mspan。
mcentral
mcentral相当于mspan的目录,每一个mcentral结构体记录了一种类别的mspan,组成一个mspan集合。
那有多少个mcentral呢?
答案是136个,因为有68种mspan类别,每种类别还分为需要GC扫描的mspan和不需要GC扫描的mspan,因此要有136个mcentral来对应所有类别的mspan。
mcentral存在哪里呢?
存在mheap结构体中。mheap有一个central字段定义了136个mcentral结构体,该central作为全局的mspan索引中心。
所以整个堆内存结构如下图:
最后,central既然是全局的mspan索引中心,当多个协程并发访问它时,造成性能怎么办?这时候就用上了mcache。
mcache
mcache是一个本地缓存,储存mspan。
要解决central的并发问题,可以参考GMP模型,每个处理器P优先通过本地队列获取协程,再考虑全局队列。
那么处理central也是相同做法,每个处理器P有一个本地缓存mcache,mcache中储存着136个mspan,即每种mspan储存一个。如下图:
这样一来,处理器P先从mcache中获取需要的mspan,如果对应mspan空间爆满了,再去全局索引中心central中交换一个空闲的过来。
总结
- 使用heapArena向操作系统申请内存
- 分配heapArena的内存时,以mspan为单位,防止碎片化
- 为了提高查找效率,使用mcentral作为同一类别mspan的中心索引
- 为了缓解并发问题,使用mcache本地缓存mspan
现在堆内存从上到下的结构为:
mheap --> heapArena --> mspan