Go 的内存管理
Contents
简介
Go 的内存分配器采用了多级内存分配模块的核心设计思想,旨在减少内存分配时锁的使用以及系统调用的次数。
在 Go中,内存管理分为栈内存管理和堆内存管理两种。栈上的内存由编译器管理,而堆上的内存则由程序管理,在运行期间进行申请和释放(垃圾回收)。变量分配在堆上还是栈上,与语法无关,主要取决于 Go 的逃逸分析。
堆上的内存空间主要用于存储较大的对象,或者需要在函数作用域外依然存活的对象。
在Go中,对于堆上内存的管理主要包括内存分配和垃圾回收两个过程。本文主要介绍 Go 中堆上内存分配的相关内容
内存分配
Go
的内存分配器借鉴了 TCMalloc
分配器的设计思想
- 一次性或者提前分配多级内存模块,减少内存分配时锁的使用和与操作系统的交互
- 多尺度内存单元,减少内存分配产生碎片
一些概念
内存单元 mspan
Go
中内存管理的基本单元,是由一片连续的 8KB 的页组成的大块内存。这里的页和操作系统本身的页并不是同个概念,它一般是操作系统页大小的几倍。
每个 mspan
按照它 span class
的大小分割成若干个object
.
span Class
等于3,object
大小就是32B。 32B大小的 object
可以存储对象大小范围在 17B~32B 的对象。而对于微小对象(小于16B),分配器会将其进行合并,将几个对象分配到同一个 object
中。
上图一组连续的浅蓝色长方形代表的是一组 Page
组成的1个 span
内存单元等级 span class
mspan
根据空间大小和分配对象的大小,被划分为 66 种等级,如下表
|
|
线程缓存 mcache
每个大小层级的 span
都会在 mcache
中保存一份
而不同的 mcache
对应不同的逻辑处理器P。由于每个 P 独享 mcache
,因此对这部分区域的访问是无锁的
中心缓存 mcentral
mcentral
是所有线程共享的缓存,需要加锁访问,它按 span class
对 span
分类,串联成链表,当 mcache
的某个级别 span
的内存被分配光时,它会向 mcentral
申请1个当前级别的 span
全局堆缓存 mheap
mheap
是堆内存的抽象,把从OS申请出的内存页组织成 Span 列表。
当 mcentral
的 Span 不够用时会向 mheap
申请,mheap
的 Span 不够用时会向OS申请。向OS的内存申请是按页来的,然后把申请来的内存页生成 Span 组织起来。
由于这部分空间是所有线程共享的,因此需要加锁访问。
heapArena
每个 heapArena
包含8K个页,即 64MB。当需要分配、回收大对象时,就从 heapArena
中操作
分配原理
小对象
小对象的内存分配,是从最低级的内存单元分配,若没有空闲的内存空间,就向上一级的内存单元申请,直到找到合适的内存单元。
从 mspan 分配对象空间
span
可以按对象大小切成很多份
随着内存的分配,span
`中的对象内存块,有些被占用,有些未被占用。
比如上图,整体代表1个 span
,蓝色块代表已被占用内存,绿色块代表未被占用内存。
当分配内存时,快速找到第一个可用的绿色块,并计算出内存地址并返回即可
当 span
内的所有内存块都被占用时,没有剩余空间继续分配对象时,mcache
会向 mcentral
申请 span
,mcache
拿到 span
后继续分配对象。
mcache 向 mcentral 申请 span
mcentral
和 mcache
一样,都是 0~131 这 132 个 span class
级别,但每个级别都保存了2个 span
链表
- nonempty:这个链表里的 span,所有 span 都至少有1个空闲的对象空间。这些 span 是 mcache 释放 span 时加入到该链表的
- empty:这个链表里的 span,所有的 span 都不确定里面是否有空闲的对象空间。当一个 span 交给 mcache 的时候,就会加入到 empty 链表
当 mcache 向 mcentral要span时,mcentral会先从nonempty搜索满足条件的span,如果没有找到再从emtpy搜索满足条件的span,然后把找到的span交给mcache。
mcentral 向 mheap 申请 span
mcentral
需要向 mheap
提供需要的内存页数和 span class
级别,然后它优先从 free 中搜索可用的 span ,如果没有找到,会从 scav 中搜索可用的 span,如果还没有找到,它会向OS申请内存
其中
- free:保存的 span 是空闲并且非垃圾回收的 span
- scav:保存的是空闲并且已经垃圾回收的 span
如果是垃圾回收导致的 span 释放,span 会被加入到 scav,否则加入到 free。
mheap 向 OS 申请内存
当 mheap
没有足够的内存时,mheap
会向OS申请额外的内存空间。
大对象
当对象需要申请的内存大于32kb时,会直接从mheap上申请内存
总结
分析 Go 的内存分配机制,总结如下
- 每次从操作系统申请一大块内存,缓存起来后续使用,以减少系统调用
- 将申请的大块内存按照特定大小预先切成大、小块,以减少内存碎片
- 为对象分配内存时,挑选大小合适的块,从中提取空间即可
- 如果对象销毁,则将对象占用的内存,归还到内存池,以便复用
Author Jakseer
LastMod 2023-03-15