1

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

 2 years ago
source link: http://tigerb.cn/2022/04/23/go-base/memory-mspan/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

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

2022-04-23

No%20Code%20No%20Life


想深入了解Go语言的内存管理实现,必然绕不开「Go内存管理单元mspan」,今天我们就通过几张图,层层深入并解开「Go内存管理单元mspan」的神秘面纱。

本文也包含的具体概念如下:

  • page的概念
  • mspan的概念
  • object的概念
  • FreeList的概念
  • sizeclass的概念
  • spanclass的概念

介绍Go内存管理单元mspan前,需要先看下page的概念,因为mspan是由N个连续page组成。

page的概念


操作系统是按page管理内存的,同样Go语言也是也是按page管理内存的,1page为8KB,保证了和操作系统一致,如下图所示:

Go内存管理单元mspan通常由N个连续page组成,如下图所示:

mspanpage组成


  • mspan可以由1个page组成
  • mspan也可以由2个连续的page组成
  • mspan也可以由3个连续的page组成
  • mspan更可以由N个连续的page组成

上图左半部分是mspan的结构体的关键字段,其中npages就代表了这个mspan是由几个连续的page组成。

除此之外,mspanmspan之间还可以构成链表,如下图所示

mspan可构成链表


这里需要注意的是:只有npages的值相同的mspan互相才可以组成一个链表。如上图所示,具体原因下文会讲。

看到这里,你会以为Go是按页page8KB为最小单位分配内存的吗?

答案:当然不是,如果这样的话会导致内存使用率不高。Go语言内存管理器会把mspan再拆解为更小粒度的单位object。如下图所示:

objectobject之间构成一个链表,大家这里肯定会想到是LinkedList,实际上并不是,因为LinkedList节点自身的指针也会占用8B内存,作为内存管理器,这部分内存会被白白浪费掉,所以这里通常使用的数据结构是FreeList

什么是FreeList?


FreeList本质上还是个LinkedList,和LinkedList的区别:

  • FreeList没有Next属性,所以不是用Next属性存放下一个节点的指针的值。
  • FreeList“相当于使用了Value的前8字节”(其实就是整块内存的前8字节)存放下一个节点的指针。
  • 分配出去的节点,节点整块内存空间可以被复写(指针的值可以被覆盖掉)

如下图所示:

所以:FreeList一个节点最小为8字节Byte

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

得到Go内存管理单元mspan被拆解为object图示如下:

到这里问题又来了,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剩余末尾浪费的内存

// class bytes/obj bytes/span objects tail waste max waste
//
// 1 8 8192 1024 0 87.50%
// 2 16 8192 512 0 43.75%
// 3 24 8192 341 8 29.24%
// 4 32 8192 256 0 21.88%
// 5 48 8192 170 32 31.52%
// 6 64 8192 128 0 23.44%
// 略...
// 62 20480 40960 2 0 6.87%
// 63 21760 65536 3 256 6.25%
// 64 24576 24576 1 0 11.45%
// 65 27264 81920 3 128 10.00%
// 66 28672 57344 2 0 4.91%
// 67 32768 32768 1 0 12.50%

sizeclassobject大小 由几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

具体图示如下:

mspan关键字段总结


挑选mspan的几个重要字段,如下图:

字段名 解释

next、prev、list mspan之间可以构成链表

startAddr mspan内存的开始位置,N个连续page内存的开始位置

npages mspan由几page组成

freeindex 空闲object链表的开始位置

nelems 一共有多少个object

spanclass 决定object的大小、以及当前mspan是否需要垃圾回收扫描

… …

Go
TIGERB

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK