9张图轻松吃透Go内存管理单元

导读 【9张图轻松吃透Go内存管理单元】想深入了解Go语言的内存管理实现,必然绕不开「Go内存管理单元mspan」,今天我们就通过几张图,层层深入并解开「Go内存管理单元mspan」的神秘面纱。
本文也包含的具体概念如下:

  • page的概念
  • mspan的概念
  • object的概念
  • FreeList的概念
  • sizeclass的概念
  • spanclass的概念
正文 介绍Go内存管理单元mspan前,需要先看下page的概念,因为mspan是由N个且连续的page组成。
page的概念
操作系统是按page管理内存的,同样Go语言也是也是按page管理内存的,1page为8KB,保证了和操作系统一致,如下图所示:
9张图轻松吃透Go内存管理单元
文章图片

Go内存管理单元mspan通常由N个且连续的page组成,如下图所示:
mspanpage组成
9张图轻松吃透Go内存管理单元
文章图片

  • mspan可以由1个page组成
  • mspan也可以由2个连续的page组成
  • mspan也可以由3个连续的page组成
  • mspan更可以由N个连续的page组成
上图左半部分是mspan的结构体的关键字段,其中npages就代表了这个mspan是由几个连续的page组成。
除此之外,mspanmspan之间还可以构成链表,如下图所示
mspan可构成链表
9张图轻松吃透Go内存管理单元
文章图片

这里需要注意的是:只有npages的值相同的mspan互相才可以组成一个链表。如上图所示,具体原因下文会讲。
看到这里,你会以为Go是按页page8KB为最小单位分配内存的吗?
答案:当然不是,如果这样的话会导致内存使用率不高。Go语言内存管理器会把mspan再拆解为更小粒度的单位object。如下图所示:
9张图轻松吃透Go内存管理单元
文章图片

objectobject之间构成一个链表,大家这里肯定会想到是LinkedList,实际上并不是,因为LinkedList节点自身的指针也会占用8B内存,作为内存管理器,这部分内存会被白白浪费掉,所以这里通常使用的数据结构是FreeList
什么是FreeList?
FreeList本质上还是个LinkedList,和LinkedList的区别:
  • FreeList没有Next属性,所以不是用Next属性存放下一个节点的指针的值。
  • FreeList“相当于使用了Value的前8字节”(其实就是整块内存的前8字节)存放下一个节点的指针。
  • 分配出去的节点,节点整块内存空间可以被复写(指针的值可以被覆盖掉)
如下图所示:
9张图轻松吃透Go内存管理单元
文章图片

所以:FreeList一个节点最小为8字节Byte
备注:因为要存指针,指针的大小为8字节,为什么?可以参考之前文章《64位平台下,指针自身的大小为什么是8字节?》

得到Go内存管理单元mspan被拆解为object图示如下:
9张图轻松吃透Go内存管理单元
文章图片

到这里问题又来了,object的具体大小是多大呢,是怎么决定的?
答案:是由sizeclass决定的。
什么是sizeclass
sizeclass是一个映射列表,实际是一个数组类型[68]uint16,它的值决定了object的大小,除此之外,mspan由几pages构成也是sizeclass值决定的。sizeclass映射列表的具体规则如下:
// 文件位置:`src/runtime/sizeclasses.g` // 索引0位置被保留使用,具体使用位置后续会讲。如上文所述,`object`之间采用freelist数据结构构成链表,指针为8Byte所以最小的object大小为8Byte字段解释: class: sizeclass值 bytes/obj: 该`mspan`拆分object大小 bytes/span: 该`mspan`是由几pages组成 objects: 该`mspan`共计包含的object数量 tail waste: 该`mspan`拆分为object之后,mspan剩余末尾浪费的内存// classbytes/objbytes/spanobjectstail wastemax waste // //1881921024087.50% //2168192512043.75% //3248192341829.24% //4328192256021.88% //54881921703231.52% //6648192128023.44% // 略... //622048040960206.87% //63217606553632566.25% //6424576245761011.45% //652726481920312810.00% //662867257344204.91% //6732768327681012.50%

sizeclass object大小 由几pages组成
0 保留 1page
1 8Byte 1pages
2 16Byte 1page
3 24Byte 1page
... ... ...
67 32KB 4pages
所以mspan结构体上只要维护一个sizeclass的字段,就可以知道该mspanobject的大小、数量。但是呢,实际上这个字段并不是sizeclass,而是spanclass,如下图所示:

那么,问题又来了。
什么是spanclass
实际上Go内存管理单元mspan被分为了两类:
  • 第一类:需要垃圾回收扫描的mspan,简称scan
  • 第二类:不需要垃圾回收扫描的mspan,简称noscan
所以说并不是所有的Go内存管理单元mspan会被垃圾回收扫描。为了区别这两类mspan,Go语言把类型标识和上面sizeclass的值一起放在了同一个字段里,具体如下:
  • sizeclass值左移一位:sizeclass << 1
  • sizeclass值最后一位存类型
    • 最后一位为1:则是不需要垃圾回收扫描的mspan
    • 最后一位为0:则是需要垃圾回收扫描的mspan
图示如下:

总结 mspan拆分object总结
这里我们以spanclass的10进制值为7的mspan为例:
spanclass10进制值为7
可得,spanclass2进制为0000 0111
可得,sizeclass7>>1:2进制0000 0011,10进制3
可得,mspan由1page组成,共计8KB(8192Byte)
可得,object大小为24Byte
可得,mspan共计包含341个object
可得,mspan尾部浪费8Byte
具体图示如下:
9张图轻松吃透Go内存管理单元
文章图片

mspan关键字段总结
挑选mspan的几个重要字段,如下图:

字段名 解释
next、prev、list mspan之间可以构成链表
startAddr mspan内存的开始位置,N个连续page内存的开始位置
npages mspan由几page组成
freeindex 空闲object链表的开始位置
nelems 一共有多少个object
spanclass 决定object的大小、以及当前mspan是否需要垃圾回收扫描
... ...
查看《Go语言轻松进阶》系列更多内容 链接 http://tigerb.cn/go/#/kernal/
9张图轻松吃透Go内存管理单元
文章图片

    推荐阅读