以下都是总结其他大佬文章,引用
内存空间包括两个重要的区域,栈区和堆区。 栈区:函数调用的参数、返回值、局部变量都会存储在这里。
堆:堆中的对象都会由内存分配器分配并由垃圾回收器回收。
当用户程序申请内存时,会通过内存分配器申请新的内存,而内存分配器将从堆中初始化对应的内存区域
分配器类型:
线性分配器:此时内存空间类似于一个数组,每次分配候指定大小内存后修改指针位置。
空闲链表分配器:
Go的堆内存布局
理解Go的内存分配分两个部分讲解
1).堆内存的结构
2).内存管理组件
第一部分先理解堆内存的构成,第二部分理解go是如何管理这些堆内存的
堆内存的结构
arena
堆上真正存放对象的地方,按页来存储对象,每页为8KB
bitmap
有两个功能:
1.用来标识arena中哪些地址保存了对象(占一位)。
2.标记此对象是否被gc标记过
spans
这一部分中存储了mspan的指针
mspan:内存管理单元,每个内存管理单元会管理几页的内存,一页的大小是8KB
Q:
为什么需要mspan?
A:
(1).如果创立了很多对象,我们遍历地址空间,最差的情况要从头遍历到尾才能找到合适的区域将其放入。因此我们将一个地址空间分为多个地址空间去管理,每个地址空间都管理不同大小的对象。这时当需要有新空间存放对象时,在最适合它大小的地址空间中找到对应的位置后将其放入。
(2).将一个地址空间分为多个不同大小的地址空间会降低空间的碎片化。即使有碎片,也会很小。
而mspan就用来管理arena中不同的地址空间
每个mspan 负责管理不同大小对象的空间。因此go的内存模块一共包含了67个跨度类,每个跨度类都存储特定大小的对象和特定数量的页数、对象。
内存管理组件
mspan
mspan是go内存管理的基本单元
①.其管理的arena片段的起始地址
②.span类型(属于哪种跨度类)
③.next和pre的双向指针
mcache
也称线程缓存
在Go的并发MGP中每次会提前给P申请一部分内存。避免了多个G内存的申请的并发安全问题,那么mcache就是P提前申请的内存。
mcache中重要的结构
1).67*2个mspan
一个存放包含指针的对象,一个存放不包含指针的对象,这样是为了方便垃圾回收,因为不包含指针的对象无需进一步扫描是否引用其他指针对象。
2).微分配器 为16字节以下的对象分配内存空间
这里引入go的对象类型,go将对象分为3类
①.微对象 size < 16B (较小的字符串)
②.小对象 16b <= size <= 32KB
③.大对象 size > 32KB
微对象和小对象都会使用线程缓存分配对象
微对象有一个专门的微型分配器,也就是在mcache中
小对象会去mcache中查找大小合适的span,当mcache中没有空间给他分配,将会去mcentral获取新的空闲内存
mcentral
也称中心缓存 为所有mcache提供切分好的mspan,当mcache中的mspan使用完后,需要向mcache申请mspan。
mcentral的结构
每个mcentral都保存了一个特定类型的mspan(67中的一种),但是这些mspan都是全局的,因此每当申请mspan时需要进行并发安全处理。
每个mcentral不仅包含了其类型还包含了两个mspan的列表。
nonempty : 指还有内存可用的list
empty : 指无内存可用的list
从mcentral获取的步骤如下:
1.加锁
2.从nonempty获取一个可用span,并将其从nonempty中删除
3.将取出的span放入empty中
4.解锁
5.线程将该mspan存入mcache
假如mcentral中没有了span,那么我们去mheap中申请内存。
mheap
也称页堆 所有对象都由该结构体统一管理,可以当做go持有的整个堆空间
结构如图
mcache是在MGP的P中,所以这里没有他,当申请的对象大于32kb,直接通过堆分配。
贴一张画的非常好的图