导读
【9张图轻松吃透Go内存管理单元】想深入了解Go语言的内存管理实现,必然绕不开「Go内存管理单元mspan
」,今天我们就通过几张图,层层深入并解开「Go内存管理单元mspan
」的神秘面纱。
本文也包含的具体概念如下:
page
的概念mspan
的概念object
的概念FreeList
的概念sizeclass
的概念spanclass
的概念
mspan
前,需要先看下page
的概念,因为mspan
是由N个且连续的page
组成。page
的概念操作系统是按
page
管理内存的,同样Go语言也是也是按page
管理内存的,1page为8KB,保证了和操作系统一致,如下图所示:文章图片
Go内存管理单元
mspan
通常由N个且连续的page
组成,如下图所示:mspan
由page
组成文章图片
mspan
可以由1个page
组成mspan
也可以由2个连续的page
组成mspan
也可以由3个连续的page
组成mspan
更可以由N个连续的page
组成
mspan
的结构体的关键字段,其中npages
就代表了这个mspan
是由几个连续的page
组成。除此之外,
mspan
和mspan
之间还可以构成链表,如下图所示mspan
可构成链表文章图片
这里需要注意的是:只有
npages
的值相同的mspan
互相才可以组成一个链表。如上图所示,具体原因下文会讲。
看到这里,你会以为Go是按页page
8KB为最小单位分配内存的吗?
答案:当然不是,如果这样的话会导致内存使用率不高。Go语言内存管理器会把mspan
再拆解为更小粒度的单位object
。如下图所示:文章图片
object
和object
之间构成一个链表,大家这里肯定会想到是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剩余末尾浪费的内存// 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
的字段,就可以知道该mspan
中object
的大小、数量。但是呢,实际上这个字段并不是sizeclass
,而是spanclass
,如下图所示:那么,问题又来了。
什么是
spanclass
?实际上Go内存管理单元
mspan
被分为了两类:- 第一类:需要垃圾回收扫描的
mspan
,简称scan
- 第二类:不需要垃圾回收扫描的
mspan
,简称noscan
mspan
会被垃圾回收扫描。为了区别这两类mspan
,Go语言把类型标识和上面sizeclass
的值一起放在了同一个字段里,具体如下:sizeclass
值左移一位:sizeclass << 1
sizeclass
值最后一位存类型
- 最后一位为1:则是不需要垃圾回收扫描的
mspan
- 最后一位为0:则是需要垃圾回收扫描的
mspan
- 最后一位为1:则是不需要垃圾回收扫描的
总结
mspan
拆分object
总结这里我们以
spanclass
的10进制值为7的mspan
为例:spanclass 10进制值为7 |
---|
可得,spanclass 2进制为0000 0111 |
可得,sizeclass 为7>>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内存架构,一个有趣的问题
- Go 语言第一课--核心篇
- GoLang底层|GoLang之切片底层系列二(浅显学习)
- 在从Golang Buffalo webapp发送推文时设置CSRF令牌时遇到问题
- 使用Air热加载Go程序(windows下的配置)
- Go语言将引入新型排序算法(pdqsort)
- golang超级mapper包 - coven
- 聊聊golang的Pseudo-versions
- 从golang-gin-realworld-example-app项目学写httpapi