图解Go语言内存分配
Go语言内置运行时(就是runtime),抛弃了传统的内存分配方式,改为自主管理。这样可以自主地实现更好的内存使用模式,比如内存池、预分配等等。这样,不会每次内存分配都需要进行系统调用。
Golang运行时的内存分配算法主要源自 Google 为 C 语言开发的TCMalloc算法
,全称Thread-Caching Malloc
。核心思想就是把内存分为多级管理,从而降低锁的粒度。它将可用的堆内存采用二级分配的方式进行管理:每个线程都会自行维护一个独立的内存池,进行内存分配时优先从该内存池中分配,当内存池不足时才会向全局内存池申请,以避免不同线程对全局内存池的频繁竞争。
为了更好的阅读体验,手动贴上文章目录:
基础概念
Go在程序启动的时候,会先向操作系统申请一块内存(注意这时还只是一段虚拟的地址空间,并不会真正地分配内存),切成小块后自己进行管理。
申请到的内存块被分配了三个区域,在X64上分别是512MB,16GB,512GB大小。
arena区域
就是我们所谓的堆区,Go动态分配的内存都是在这个区域,它把内存分割成8KB
大小的页,一些页组合起来称为mspan
。
bitmap区域
标识arena
区域哪些地址保存了对象,并且用4bit
标志位表示对象是否包含指针、GC
标记信息。bitmap
中一个byte
大小的内存对应arena
区域中4个指针大小(指针大小为 8B )的内存,所以bitmap
区域的大小是512GB/(4*8B)=16GB
。
从上图其实还可以看到bitmap的高地址部分指向arena区域的低地址部分,也就是说bitmap的地址是由高地址向低地址增长的。
spans区域
存放mspan
(也就是一些arena
分割的页组合起来的内存管理基本单元,后文会再讲)的指针,每个指针对应一页,所以spans
区域的大小就是512GB/8KB*8B=512MB
。除以8KB是计算arena
区域的页数,而后乘以8是计算spans
区域所有指针的大小。创建mspan
的时候,按页填充对应的spans
区域,在回收object
时,根据地址很容易就能找到它所属的mspan
。
内存管理单元
mspan
:Go中内存管理的基本单元,是由一片连续的8KB
的页组成的大块内存。注意,这里的页和操作系统本身的页并不是一回事,它一般是操作系统页大小的几倍。一句话概括:mspan
是一个包含起始地址、mspan
规格、页的数量等内容的双端链表。
每个mspan
按照它自身的属性Size Class
的大小分割成若干个object
,每个object
可存储一个对象。并且会使用一个位图来标记其尚未使用的object
。属性Size Class
决定object
大小,而mspan
只会分配给和object
尺寸大小接近的对象,当然,对象的大小要小于object
大小。还有一个概念:Span Class
,它和Size Class
的含义差不多,
Size_Class = Span_Class / 2
相关文章