超全代码!详解Go中内存分配源码实现
导语|本文会从调试汇编入手讲解Go的内存分配实现的源码 , 所以在看的时候不妨自己动手调试 , 并且文中含有大量高质量的图片帮助理解 , 耐心看完一定能有所收获 。 注:本文使用的go源码为15.7 。
一、介绍
Go语言的内存分配器借鉴了TCMalloc的设计实现高速的内存分配 , 它的核心理念是使用多级缓存将对象根据大小分类 , 并按照类别实施不同的分配策略 。 TCMalloc相关的信息可以看这里:
即如果要分配的对象是个小对象(
如下:对象被分到不同的内存大小组中的链表中 。
文章图片
如果是个大对象(>32k) , 那么页堆进行分配 。 如下:
文章图片
虽然go内存分配器最初是基于tcmalloc的 , 但是现在已经有了很大的不同 。 所以上面的一些结构会有些许变化 , 下面会提及 。
因为内存分配的源码比较复杂 , 为了方便大家调试 , 所以在进行源码分析之前 , 先看看如何断点汇编进行调试 。
1.断点调试汇编
目前Go语言支持GDB、LLDB和Delve几种调试器 。 只有Delve是专门为Go语言设计开发的调试工具 。 而且Delve本身也是采用Go语言开发 , 对Windows平台也提供了一样的支持 。 本节我们基于Delve简单解释如何调试Go汇编程序 。 项目地址:https://github.com/go-delve/delve
安装:
首先编写一个test.go的例子:
然后命令行进入包所在目录 , 然后输入dlvdebug命令进入调试:
然后可以使用break命令在main包的main方法上设置一个断点:
通过breakpoints查看已经设置的所有断点:
通过continue命令让程序运行到下一个断点处:
通过disassemble反汇编命令查看main函数对应的汇编代码:
现在我们可以使用break断点到runtime.newobject函数的调用上:
输入continue跳到断点的位置:
print命令来查看typ的数据:
可以看到这里打印的size是16bytes , 因为我们A结构体里面就一个string类型的field 。
进入到mallocgc方法后 , 通过args和locals命令查看函数的参数和局部变量:
2.各个对象入口
我们根据汇编可以判断 , 所有的函数入口都是runtime.mallocgc , 但是下面两个对象需要注意一下:
int64对象
runtime.convT64
这段代码表示如果一个int64类型的值小于256 , 直接使用的是缓存值 , 那么这个值不会进行内存分配 。
string对象
runtime.convTstring
由这段代码显示 , 如果是创建一个为”“的string对象 , 那么会直接返回一个固定的地址值 , 不会进行内存分配 。
3.调试用例
大家在调试的时候也可以使用下面的例子来进行调试 , 因为go里面的对象分配是分为大对象、小对象、微对象的 , 所以下面准备了三个方法分别对应三种对象的创建时的调试 。
二、分析
1.分配器的组件
内存分配是由内存分配器完成 , 分配器由3种组件构成:runtime.mspan、runtime.mcache、runtime.mcentral、runtime.mheap 。
runtime.mspan
runtime.mspan是内存管理器里面的最小粒度单元 , 所有的对象都被管理在mspan下 。
mspan是一个链表 , 有上下指针;
npages代表mspan管理的堆页的数量;
freeindex是空闲对象的索引;
nelems代表这个mspan中可以存放多少对象 , 等于(npages*pageSize)/elemsize;
allocCache用于快速的查找未被使用的内存地址;
elemsize表示一个对象会占用多个bytes , 等于class_to_size[sizeclass] , 需要注意的是sizeclass每次获取的时候会sizeclass方法 , 将sizeclass>>1;
limit表示span结束的地址值 , 等于startAddr+npages*pageSize;
实例图如下:
文章图片
图中alloc是一个拥有137个元素的mspan数组 , mspan数组管理数个page大小的内存 , 每个page是8k , page的数量由spanclass规格决定 。
runtime.mcache
runtime.mcache是绑在并发模型GPM的P上 , 在分配微对象和小对象的时候会先去runtime.mcache中获取 , 每一个处理器都会被分配一个线程缓存runtime.mcache , 因此从runtime.mcache进行分配时无需加锁 。
在runtime.mcache中有一个alloc数组 , 是runtime.mspan的集合 , runtime.mspan是Go语言内存管理的基本单元 。 对于[16B,32KB]的对象会使用这部分span进行内存分配 , 所以所有在这区间大小的对象都会从alloc这个数组里寻找 , 下面会分析到 。
runtime.mcentral
当runtime.mcache中空间不足的时候 , 会去runtime.mcentral中申请对应规格的mspan 。 获取mspan的时候会从partial列表和full列表中获取 , 获取的时候会使用无锁的方式获取 。
- 神秘的宇宙射线事故3:宇宙射线可改变代码,难道世界是虚拟的?
- 神秘的宇宙射线事故2:宇宙射线击中选票机,改变代码干扰选举?
- 世界的尽头真的是一堆代码?科学家指出逻辑缺口,知情者感到害怕
- 详解KK集团200亿估值玄机:精妙财务,绝妙加盟
- 在英特尔(纳斯达克股票代码:INTC)最近公布第三季度业绩之前|投资机会大于风险,英特尔公允价值为80美元
- 中国经济网北京11月2日讯 海南矿业(证券代码:601969)昨日晚间发布公告称|海南矿业:股东复星产投拟减持不超6065.11万股
- 中国经济网北京11月2日讯 中闽能源(证券代码:600163)昨日晚间发布公告称|中闽能源:股东华兴创投等拟共减持不超3848.37万股
- 宁德时代三季报详解,这四个指标值得关注|见智研究
- 每毫升60万!详解茅台酒酒王-汉帝茅台
- 火箭发射后为何要喷水?400吨水瞬间喷射只为降温,详解背后原理