golang的内存管理

参考链接一
参考链接二
参考链接三
参考链接四
参考链接五

虚拟内存空间:堆区和栈区
栈区:函数调用的参数、返回值、局部变量,这部分内存由编译器管理
堆区:堆中的对象由内存分配器分配,垃圾收集器回收

内存管理的三个组件:用户程序、分配器、收集器
用户程序通过分配器在堆上初始化内存空间在这里插入图片描述

一、分配方法

线性分配器、空闲链表分配器

1、线性分配:只需维护指针

在这里插入图片描述
缺点:无法在对象释放后重用该空间在这里插入图片描述

2、空闲链表分配

可重用被释放的内存,内部维护空闲链表(数据结构),用户程序申请内存时会遍历链表找到足够大的内存块
在这里插入图片描述
类似策略:隔离适应,将内存划分为多个内存块大小不同的链表,单个链表内内存块大小相同,优点是减少了遍历内存块数量,提高效率

3、分级分配:Golang

Golang内存分配器根据申请分配内存的大小选择不同的处理逻辑,将对象分为三类

类别大小
微对象(0,16B)
小对象[16B,32KB]
大对象(32KB,+∞)

多级缓存

内存分配器不仅区别对待大小不同的对象,还会将内存分为不同的级别管理:线程缓存(Thread Cache)中心缓存(Central Cache)页堆(Page Heap)三级
线程缓存属于单个线程,不涉及多线程,因此不用加锁,如果线程缓存不满足分配,小对象由中心缓存分配,大对象直接放入页堆,与操作系统的多级缓存类似
在这里插入图片描述

二、虚拟内存布局

Go在1.10以前的版本堆区的内存空间是连续的,但在1.11版本,使用稀疏的堆内存空间取代了连续内存

线性内存

1.10版本之前,程序启动前会初始化虚拟内存空间,spans、bitmap、arena
在这里插入图片描述 spans:存储了指向内存管理单元runtime.mspan的指针,每个内存单元由n*8KB的页组成
bitmap:用于标识arena的那些地址保存了对象,位图中的每个byte都会表示堆区中的32byte是否空闲
arena:真正的堆区,运行时8K为一页
任何一个地址,可以通过arena的基地址计算页数,并通过spans数组找到对应的runtime.mspan,spans数组中多个连续的位置可能对应同一个mspan。Go的垃圾回收会通过指针的地址找到管理该对象的runtime.mspan。
问题: C和Go混合使用会导致程序崩溃

稀疏内存

移除堆内存512G的上限,解决了C和Go混合使用的地址冲突问题

在这里插入图片描述
二维稀疏内存,使用二维的runtime.heapArena数组管理内存,每个单元管理64MB的内存空间:

type heapArena struct {
	//是否空闲
	bitmap       [heapArenaBitmapBytes]byte	
	//内存单元
	spans        [pagesPerArena]*mspan		
	pageInUse    [pagesPerArena / 8]uint8
	pageMarks    [pagesPerArena / 8]uint8
	pageSpecials [pagesPerArena / 8]uint8
	checkmarks   *checkmarksMap
	//该结构体的基地址
	zeroedBase   uintptr					
}

在这里插入图片描述

三、Go内存管理组件

对应的数据结构
内存管理单元 – runtime.mspan
线程缓存 – runtime.mcache
中心缓存 – runtime.mcentral
页堆 – runtime.mheap

在这里插入图片描述

内存管理单元-mspan

//go:notinheap
type mspan struct {
   next *mspan     //链表下一个span地址
   prev *mspan     // 链表前一个span地址
   list *mSpanList // 链表地址

   startAddr uintptr // 该span在arena区域的起始地址
   npages    uintptr // 该span占用arena区域page的数量

   manualFreeList gclinkptr // 空闲对象列表

   freeindex uintptr//freeindex是0到nelems之间的位置索引,标记下一个空对象索引

   nelems uintptr // 管理的对象数

   allocCache uint64   //从freeindex开始的位标记
   allocBits  *gcBits //该mspan中对象的位图
   gcmarkBits *gcBits //该mspan中标记的位图,用于垃圾回收
   sweepgen    uint32 //扫描计数值,用户与mheap的sweepgen比较,根据差值确定该span的扫描状态

   allocCount  uint16     // 已分配的对象的个数
   spanclass   spanClass  // span分类
   state       mSpanState // mspaninuse etc
   needzero    uint8      // 分配之前需要置零

   scavenged   bool       // 标记是否内存已经被系统回收,大对象会用到
   elemsize    uintptr    // 对象的大小
   unusedsince int64      // 空闲状态开始的纳秒值时间戳,用于系统内存释放
   limit       uintptr    // 申请大对象内存块会用到,mspan的数据截止位置

Go语言内存管理的基本单元,包含前后指针,运行时用mSpanList串联
在这里插入图片描述
mspan会根据spanclass把空间划分为一个个对象,分配空间就是把这一个个小对象分配出去,其中freeindex记录了第一个空对象。

在这里插入图片描述
spanclass一共有67种
spanclass对应的空间大小

线程缓存-mcache

type p struct {
	···
	mcache	*mcache
	···
}
//go:notinheap
type mcache struct { 

   tiny             uintptr //<16byte 申请小对象的起始地址
   tinyoffset       uintptr //从起始地址tiny开始的偏移量
   tinyAllocs 		uintptr //tiny对象分配的数量   

   alloc [numSpanClasses]*mspan // 分配的mspan list,其中numSpanClasses=134(实际上是68个,),索引是splanclassId

   stackcache [_NumStackOrders]stackfreelist //栈缓存

   flushGen uint32 //扫描计数,gc要用
}
  • mcache会和处理器(P)绑定,用来给协程分配且只分配小对象,其中小对象还会细分为上述的size-class.
  • mcache是不会被GC的区域,所以GC时需要将mcache放到central cache中来回收。
  • mcache只会给一个P使用,且一个P只会和一个M绑定,因此没有竞争,不需要加锁。
  • mcache在初始化时不会分配空间,会在运行期间动态分配,如下图,小对象(16B~32KB)会在mcache中分配,分配的时候还会细分68个span-class(就是上面的alloc属性,当然其中有的不是小对象,忽略),不够的话从对应span-class的mcentral申请;微对象会在heap中分配,mcache中有指向这块空间的指针,在mark termination阶段回收;大对象(>32KB)会直接在mheap中分配。

在这里插入图片描述

中心缓存-mcentral

上面说mcache会动态申请空间,就是在mcentral中申请,每个mcentral都有对应的span-class,mheap中有68*2个mcentral
//go:notinheap
type mcentral struct {
	spanclass spanClass

	// 这个原注解“a free object”就很魔性,按照我对google内存分配tcmalloc算法的理解,不论是thread cache还是central cache,如果空间不够就会从上层申请,但如果空间太多又会返回给上层,所以应该是每个spanclass都会保持一个空的object,这样就避免浪费空间,也能满足分配
	partial [2]spanSet // list of spans with a free object
	full    [2]spanSet // list of spans with no free objects
}
// A spanSet is a set of *mspans.
//
// spanSet is safe for concurrent push and pop operations.
type spanSet struct {
	spineLock mutex
	spine     unsafe.Pointer 
	spineLen  uintptr       
	spineCap  uintptr        
	
	index headTailIndex
}

type mheap struct {
	···
	central [numSpanClasses]struct {
		mcentral mcentral
		pad      [cpu.CacheLinePadSize - unsafe.Sizeof(mcentral{})%cpu.CacheLinePadSize]byte
	}
	···
  • mcentral中有两个列表,分别是空闲列表和已经分配的列表;
  • 当mcentral中也没有空闲区域时,就会从mheap中申请span,然后将span切分,增加到mcentral的空闲列表中;

堆-mheap

堆区的属性我还没完全理清,就不贴代码了
  • 按上面所说,mcentral中空间不够后,会从mheap中取span,然后切分;
  • mheap空间也不够的话,就会从操作系统申请;
  • 上面所说的稀疏矩阵也有用到,分成一个个heapArena
    在这里插入图片描述

四、内存分配总结

所有对象均由runtime.newObject函数分配,该函数调用runtime.mallocgc分配指定大小的空间,按上述分级分配,分成三种对象:微对象、小对象、大对象。
微对象:在heap分配,有一个指针指向它,在mark termination阶段回收。
小对象:mcache中分配,细分为68中spanclass,不够从central cache申请
大对象:直接在mheap分配,不够从操作系统申请

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值