go内存分配

1、内存管理

在这里插入图片描述
以上是程序内存的逻辑分类情况。
我们再来看看一般程序的内存的真实(真实逻辑)图:
在这里插入图片描述

2、Go的内存分配

2.1Go的内存分配核心思想

Go是内置运行时的编程语言(runtime),像这种内置运行时的编程语言通常会抛弃传统的内存分配方式,改为自己管理。这样可以完成类似预分配内存池等操作,以避开系统调用带来的性能问题防止每次分配内存都需要系统调用

Go的内存分配的核心思想可以分为以下几点:
(1)每次从操作系统申请一大块儿的内存,由Go来对这块儿内存做分配,减少系统调用
(2)内存分配算法采用Google的TCMalloc算法。算法比较复杂,究其原理可自行查阅。其核心思想就是把内存切分的非常的细小,分为多级管理,以降低锁的粒度。
(3)回收对象内存时,并没有将其真正释放掉,只是放回预先分配的大块内存中,以便复用。只有内存闲置过多的时候,才会尝试归还部分内存给操作系统,降低整体开销

2.2 Go的内存结构

在这里插入图片描述
Go在程序启动的时候,会分配一块连续的内存(虚拟内存)。整体如下:
在这里插入图片描述
图中span和bitmap的大小会随着heap的改变而改变

arena
arena区域就是我们通常所说的heap。
heap中按照管理和使用两个维度可认为存在两类“东西”:

一类是从管理分配角度,由多个连续的页(page)组成的大块内存:
在这里插入图片描述
另一类是从使用角度出发,就是平时咱们所了解的:heap中存在很多"对象":
在这里插入图片描述
spans
spans区域,可以认为是用于上面所说的管理分配arena(即heap)的区域。
此区域存放了mspan的指针,mspan是啥后面会讲。
spans区域用于表示arena区中的某一页(page)属于哪个mspan
在这里插入图片描述
mspan可以说是go内存管理的最基本单元,但是内存的使用最终还是要落脚到“对象”上。mspan和对象是什么关系呢?
其实“对象”肯定也放到page中,毕竟page是内存存储的基本单元。

我们抛开问题不看,先看看一般情况下的对象和内存的分配是如何的:如下图
在这里插入图片描述
假如再分配“p4”的时候,是不是内存不足没法分配了?是不是有很多碎片?

这种一般的分配情况会出现内存碎片的情况,go是如何解决的呢?

可以归结为四个字:按需分配。go将内存块分为大小不同的67种,然后再把这67种大内存块,逐个分为小块(可以近似理解为大小不同的相当于page)称之为span(连续的page),在go语言中就是上文提及的mspan
在这里插入图片描述
对象分配的时候,根据对象的大小选择大小相近的span,这样,碎片问题就解决了。
bitmap
bitmap 有好几种:Stack, data, and bss bitmaps,再就是这次要说的heap bitmaps
在此bitmap的做作用是标记标记arena(即heap)中的对象。一是的标记对应地址中是否存在对象,另外是标记此对象是否被gc标记过。一个功能一个bit位,所以,heap bitmaps两个bit位
bitmap区域中的一个byte对应arena区域的四个指针大小的内存的结构如下:

在这里插入图片描述
bitmap的地址是由高地址向低地址增长的。

宏观的图为:
在这里插入图片描述
bitmap 主要的作用还是服务于GC。

arena中包含基本的管理单元和程序运行时候生成的对象或实体,这两部分分别被spansbitmap这两块非heap区域的内存所对应着。
逻辑图如下:
在这里插入图片描述
spans和bitmap都会根据arena的动态变化而动态调整大小。

2.3内存管理组件

go的内存管理组件主要有:mspanmcachemcentralmheap
(1)mspan为内存管理的基础单元,直接存储数据的地方。
(2)mcache:每个运行期的goroutine都会绑定的一个mcache(具体来讲是绑定的GMP并发模型中的P,所以可以无锁分配mspan,后续还会说到),mcache会分配goroutine运行中所需要的内存空间(即mspan)。
(3)mcentral为所有mcache切分好后备的mspan
(4)mheap代表Go程序持有的所有堆空间。还会管理闲置的span,需要时向操作系统申请新内存。
mspan
在这里插入图片描述
mspan是go中内存管理的基本单元,在上文spans中其实已经做了详细的解说,在此就不在赘述了。
mcache
为了避免多线程申请内存时不断的加锁,goroutine为每个线程分配了span内存块的缓存,这个缓存即是mcache,每个goroutine都会绑定的一个mcache,各个goroutine申请内存时不存在锁竞争的情况。
然后请看下图:
在这里插入图片描述
大体上就是上图这个样子了。注意看我们的mcache在哪儿呢?就在P上!
知道为什么没有锁竞争了吧,因为运行期间一个goroutine只能和一个P关联,而mcache就在P上,所以,不可能有锁的竞争。
我们再来看看mcache具体的结构:
在这里插入图片描述
mcache中的span链表分为两组,一组是包含指针类型的对象,另一组是不包含指针类型的对象。为什么分开呢?

主要是方便GC,在进行垃圾回收的时候,对于不包含指针的对象列表无需进一步扫描是否引用其他活跃的对象。
对于 <=32k的对象,将直接通过mcache分配。
在此,我觉的有必要说一下go中对象按照的大小维度的分类。
分为三类:

tinny allocations (size < 16 bytes,no pointers)
small allocations (16 bytes < size <= 32k)
large allocations (size > 32k)

前两类:tiny allocationssmall allocations是直接通过mcache来分配的。

对于tiny allocations的分配,有一个微型分配器tiny allocator来分配,分配的对象都是不包含指针的,例如一些小的字符串和不包含指针的独立的逃逸变量等。

small allocations的分配,就是mcache根据对象的大小来找自身存在的大小相匹配mspan来分配。
mcach没有可用空间时,会从mcentralmspans 列表获取一个新的所需大小规格的mspan
mcentral
为所有mcache提供切分好的mspan
每个mcentral保存一种特定类型的全局mspan列表,包括已分配出去的和未分配出去的。
在这里插入图片描述
还记得mspan的67种类型吗?有多少种类型的mspan就有多少个mcentral。

每个mcentral都会包含两个mspan的列表:

没有空闲对象或mspan已经被mcache缓存的mspan列表(empty mspanList)
有空闲对象的mspan列表(empty mspanList)
由于mspan是全局的,会被所有的mcache访问,所以会出现并发性问题,因而mcentral会存在一个锁。

单个的mcentral结构如下:
在这里插入图片描述
假如需要分配内存时,mcentral没有空闲的mspan列表了,此时需要向mheap去获取。
mheap
mheap可以认为是Go程序持有的整个堆空间mheap全局唯一,可以认为是个全局变量。
其结构如下:
在这里插入图片描述
mheap包含了除了上文中讲的mcache之外的一切,mcache是存在于Go的GMP调度模型的P中的。

我们知道,大于32K的对象被定义为大对象,直接通过mheap 分配。这些大对象的申请是由mcache发出的,而mcache在P上,程序运行的时候往往会存在多个P,因此,这个内存申请是并发的;所以为了保证线程安全,必须有一个全局锁

假如需要分配的内存时,mheap中也没有了,则向操作系统申请一系列新的页(最小 1MB)。
学习链接:
https://www.jianshu.com/p/2904efc7f1a8

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值