Go 堆内存结构是什么样的?

499 阅读5分钟

前言

Go中栈内存也是从堆内存中申请的,那堆内存是什么结构呢?堆内存如何分配内存呢?

操作系统内存结构

操作系统中,每个进程都有一个独立的巨大的虚拟内存,这些内存实际映射到物理内存上。而当物理内存空间不够时,就会触发oom,杀掉一个进程来释放内存。

图示如下: image.png

而Go运行时也是一个进程,也会拥有一片巨大的虚拟内存,那Go是怎么使用的呢?

heapArena

Go把虚拟内存视为堆内存,并进行分块,每一块单元大小为64MB。每个单元对应一个heapArena结构体来描述单元信息。

那虚拟内存为256TB,每个单元大小为64MB,理论上能有2^20个单元和heapArena。另外,所有的heapArena都储存在mheap结构体中。图示如下:

image.png

当然一般只申请几百个heapArena,远远小于理论值。

heapArena如何分配内存

一个heapArena对应单元大小为64MB,分配内存时结构如下:

image.png 那每次分配内存给一个对象时,该怎么安排它在内存中的位置呢?

线性分配

简单方案是线性分配,每个Object进入都挨着最后一个Object的位置放下。

如果有GC清理了Object,则将空闲碎片空间加入链表中。

下一个进入的Object大小如果小于链表的空闲空间,则优先放入链表位置,否则仍线性分配。图示如下:

image.png

新进入的Object如果碎片空间足够,优先放置。

image.png

新进入的Object因空间碎片过小放不下,就顺序放置。

但是,如果所有新进入的Object都比空间碎片大,那链表中的空闲块就一直存在,无法利用而浪费空间。故该方案不采用。

分级分配

分级分配会预先将heapArena划分为不同大小的块,如下图:

image.png

第一行有八个同样大小的块,第二行有四个,以此类推。什么用意呢?这样每个进来的Object会根据自己的大小来选择刚好能放下自己的空间。如下图:

image.png

虽然划分区中仍有部分空间碎片,但这很小。这样一来,每个Object都放置到最适合自己的空间上,极大的避免了空间浪费。

mspan

基于分级分配的策略,Go将每一级视为最小空间单位mspan。如下图:

image.png

上图有四级则有四种类别的mspan,那Go种总共有多少种mspan呢?如下图:

image.png

答案是一共有68种,上图隐藏了class0类别。解释下上图意思,如第一种,每个单元有8bytes,一共有1024个单元,那么该span总共有8192个字节,即8KBmax 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,该如何查找?

整体内存结构:

image.png

从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索引中心

所以整个堆内存结构如下图:

image.png

最后,central既然是全局的mspan索引中心,当多个协程并发访问它时,造成性能怎么办?这时候就用上了mcache

mcache

mcache是一个本地缓存,储存mspan。

要解决central的并发问题,可以参考GMP模型,每个处理器P优先通过本地队列获取协程,再考虑全局队列。

那么处理central也是相同做法,每个处理器P有一个本地缓存mcache,mcache中储存着136个mspan,即每种mspan储存一个。如下图:

image.png

这样一来,处理器P先从mcache中获取需要的mspan,如果对应mspan空间爆满了,再去全局索引中心central中交换一个空闲的过来。

总结

  • 使用heapArena向操作系统申请内存
  • 分配heapArena的内存时,以mspan为单位,防止碎片化
  • 为了提高查找效率,使用mcentral作为同一类别mspan的中心索引
  • 为了缓解并发问题,使用mcache本地缓存mspan

现在堆内存从上到下的结构为:

mheap --> heapArena --> mspan