在Go语言里,从内存的分配到不再使用后内存的回收等等这些内存管理工作都是由Go在底层完成的。虽然开发者在写代码时不必过度关心内存从分配到回收这个过程,但是Go的内存分配策略里有不少有意思的设计,通过了解他们有助于我们自身的提高,也让我们能写出更高效的Go程序。

Go内存管理的设计旨在在并发环境中快速运行,并与垃圾回收器集成在一起。让我们看一个简单的示例:

package main

type smallStruct struct {
a, b int64
c, d float64
} func main() {
smallAllocation()
} //go:noinline
func smallAllocation() *smallStruct {
return &smallStruct{}
}

函数上面的注释//go:noinline将禁止Go对该函数进行内联,这样main函数就会使用smallAllocation函数返回的指针变量,因为被多个函数使用,返回的这个变量将被分配到堆上。

关于内联的概念之前的文章有说过:

内联是一种手动或编译器优化,用于将简短函数的调用替换为函数体本身。这么做的原因是它可以消除函数调用本身的开销,也使得编译器能更高效地执行其他的优化策略。

所以如果上面的例子不干预编译器的话,编译器通过内联将smallAllocation函数体里的内容直接放到main函数里,这样就不会产生smallAllocation这个函数的调用了,所有的变量都是main函数内这个范围使用的,也就不在需要将变量往堆上分配了。

继续说上面那个例子,通过逃逸分析命令 go tool compile -m main.go 可以确认我们上面的分析,&smallStruct{}会被分配到堆上去。

➜ go tool compile -m main.go
main.go:12:6: can inline main
main.go:10:9: &smallStruct literal escapes to heap

借助命令go tool compile -S main.go,可以显示该程序的汇编代码,也可以明确地向我们展示内存的分配:

0x001d 00029 (main.go:10)       LEAQ    type."".smallStruct(SB), AX
0x0024 00036 (main.go:10) PCDATA $2, $0
0x0024 00036 (main.go:10) MOVQ AX, (SP)
0x0028 00040 (main.go:10) CALL runtime.newobject(SB)

内置函数newobject会通过调用另外一个内置函数mallocgc在堆上分配新内存。在Go里面有两种内存分配策略,一种适用于程序里小内存块的申请,另一种适用于大内存块的申请,大内存块指的是大于32KB。

下面我们来细聊一下这两种策略。

小于32KB内存块的分配策略

当程序里发生了32kb以下的小块内存申请时,Go会从一个叫做的mcache的本地缓存给程序分配内存。这个本地缓存mcache持有一系列的大小为32kb的内存块,这样的一个内存块里叫做mspan,它是要给程序分配内存时的分配单元。

在Go的调度器模型里,每个线程M会绑定给一个处理器P,在单一粒度的时间里只能做多处理运行一个goroutine,每个P都会绑定一个上面说的本地缓存mcache。当需要进行内存分配时,当前运行的goroutine会从mcache中查找可用的mspan。从本地mcache里分配内存时不需要加锁,这种分配策略效率更高。

那么有人就会问了,有的变量很小就是数字,有的却是一个复杂的结构体,申请内存时都分给他们一个mspan这样的单元会不会产生浪费。其实mcache持有的这一系列的mspan并不都是统一大小的,而是按照大小,从8字节到32KB分了大概70类的msapn

就文章开始的那个例子来说,那个结构体的大小是32字节,正好32字节的这种mspan能满足需求,那么分配内存的时候就会给它分配一个32字节大小的mspan

现在,我们可能会好奇,如果分配内存时mcachce里没有空闲的32字节的mspan了该怎么办?Go里还为每种类别的mspan维护着一个mcentral

mcentral的作用是为所有mcache提供切分好的mspan资源。每个central会持有一种特定大小的全局mspan列表,包括已分配出去的和未分配出去的。 每个mcentral对应一种mspan,当工作线程的mcache中没有合适(也就是特定大小的)的mspan时就会从mcentral 去获取。mcentral被所有的工作线程共同享有,存在多个goroutine竞争的情况,因此从mcentral获取资源时需要加锁。

mcentral的定义如下:

//runtime/mcentral.go

type mcentral struct {
// 互斥锁
lock mutex // 规格
sizeclass int32 // 尚有空闲object的mspan链表
nonempty mSpanList // 没有空闲object的mspan链表,或者是已被mcache取走的msapn链表
empty mSpanList // 已累计分配的对象个数
nmalloc uint64
}

mcentral里维护着两个双向链表,nonempty表示链表里还有空闲的mspan待分配。empty表示这条链表里的mspan都被分配了object

如果上面我们那个程序申请内存的时候,mcache里已经没有合适的空闲mspan了,那么工作线程就会像下图这样去mcentral里去申请。

简单说下mcachemcentral获取和归还mspan的流程:

  • 获取 加锁;从nonempty链表找到一个可用的mspan;并将其从nonempty链表删除;将取出的mspan加入到empty链表;将mspan返回给工作线程;解锁。
  • 归还 加锁;将mspanempty链表删除;将mspan加入到nonempty链表;解锁。

mcentral没有空闲的mspan时,会向mheap申请。而mheap没有资源时,会向操作系统申请新内存。mheap主要用于大对象的内存分配,以及管理未切割的mspan,用于给mcentral切割成小对象。

同时我们也看到,mheap中含有所有规格的mcentral,所以,当一个mcachemcentral申请mspan时,只需要在独立的mcentral中使用锁,并不会影响申请其他规格的mspan

上面说了每种尺寸的mspan都有一个全局的列表存放在mcentral里供所有线程使用,所有mcentral的集合则是存放于mheap中的。 mheap里的arena 区域是真正的堆区,运行时会将 8KB 看做一页,这些内存页中存储了所有在堆上初始化的对象。运行时使用二维的 runtime.heapArena 数组管理所有的内存,每个 runtime.heapArena 都会管理 64MB 的内存。

如果 arena 区域没有足够的空间,会调用 runtime.mheap.sysAlloc 从操作系统中申请更多的内存。

大于32KB内存块的分配策略

Go没法使用工作线程的本地缓存mcache和全局中心缓存mcentral上管理超过32KB的内存分配,所以对于那些超过32KB的内存申请,会直接从堆上(mheap)上分配对应的数量的内存页(每页大小是8KB)给程序。

总结

我们把内存分配管理涉及的所有概念串起来,可以勾画出Go内存管理的一个全局视图:

Go语言的内存分配非常复杂,这个文章从一个比较粗的角度来看Go的内存分配,并没有深入细节。一般而言,了解它的原理,到这个程度也就可以了(应付面试)。

总结起来关于Go内存分配管理的策略有如下几点:

  • Go在程序启动时,会向操作系统申请一大块内存,由mheap结构全局管理。
  • Go内存管理的基本单元是mspan,每种mspan可以分配特定大小的object
  • mcachemcentralmheapGo内存管理的三大组件,mcache管理线程在本地缓存的mspanmcentral管理全局的mspan供所有线程使用;mheap管理Go的所有动态分配内存。
  • 一般小对象通过mspan分配内存;大对象则直接由mheap分配内存。

Go语言内存分配(简述 转)的更多相关文章

  1. C语言内存分配方法。

    当C程序运行在操作系统上时,操作系统会给每一个程序分配一定的栈空间. 堆为所有程序共有的,需要时需要申请访问. 一.栈 局部变量.函数一般在栈空间中. 运行时自动分配&自动回收:栈是自动管理的 ...

  2. 【嵌入式开发】C语言 内存分配 地址 指针 数组 参数 实例解析

    . Android源码看的鸭梨大啊, 补一下C语言基础 ... . 作者 : 万境绝尘 转载请注明出处 : http://blog.csdn.net/shulianghan/article/detai ...

  3. 图解Go语言内存分配

    目录 基础概念 内存管理单元 内存管理组件 mcache mcentral mheap 内存分配流程 总结 参考资料 Go语言内置运行时(就是runtime),抛弃了传统的内存分配方式,改为自主管理. ...

  4. 内存管理 垃圾回收 C语言内存分配 垃圾回收3大算法 引用计数3个缺点

    小结: 1.垃圾回收的本质:找到并回收不再被使用的内存空间: 2.标记清除方式和复制收集方式的对比: 3.复制收集方式的局部性优点: https://en.wikipedia.org/wiki/C_( ...

  5. 关于c语言内存分配,malloc,free,和段错误,内存泄露

    1.   C语言的函数malloc和free (1) 函数malloc和free在头文件<stdlib.h>中的原型及参数        void * malloc(size_t size ...

  6. Go语言内存分配机制

    前言: 本文是学习<<go语言程序设计>> -- 清华大学出版社(王鹏 编著) 的2014年1月第一版 做的一些笔记 , 如有侵权, 请告知笔者, 将在24小时内删除, 转载请 ...

  7. C语言 内存分配 地址 指针 数组 参数 实例解析

    . Android源码看的鸭梨大啊, 补一下C语言基础 ... . 作者 : 万境绝尘 转载请注明出处 : http://blog.csdn.net/shulianghan/article/detai ...

  8. C语言内存分配函数malloc——————【Badboy】

    C语言中经常使用的内存分配函数有malloc.calloc和realloc等三个,当中.最经常使用的肯定是malloc,这里简单说一下这三者的差别和联系. 1.声明 这三个函数都在stdlib.h库文 ...

  9. C语言内存分配

      (1)代码区(text segment).存放CPU执行的机器指令(machine instructions).通常,代码区是可共享的 (即另外的执行程序可以调用它),因为对于频繁被执行的程序,只 ...

随机推荐

  1. UDP协议网络Socket编程(java实现C/S通信案例)

    我的博客园:https://www.cnblogs.com/chenzhenhong/p/13825286.html 我的CSDN博客:https://blog.csdn.net/Charzous/a ...

  2. day28 Pyhton 面向对象 继承

    1.昨日回顾 类的命名空间 静态属性\动态属性(方法) 对象的命名空间 #对象的属性 #类指针:对象能够通过这个类指针找到类 #静态属性:属于类,多个对象共享这个资源 #尽量用类名来操作静态属性 #对 ...

  3. Jenkins集成appium自动化测试(Windows篇)

    一,引入问题 自动化测试脚本绝大部分用于回归测试,这就需要制定执行策略,如每天.代码更新后.项目上线前定时执行,才能达到最好的效果,这时就需要进行Jenkins集成. 不像web UI自动化测试可以使 ...

  4. k8s-命令创建service

    查看命令帮助 [root@master kubernetes]# kubectl create service -h Create a service using specified subcomma ...

  5. spring boot:用dynamic-datasource-spring-boot-starter配置druid多数据源(spring boot 2.3.3)

    一,dynamic-datasource-spring-boot-starter的用途? 1,dynamic-datasource-spring-boot-starter 是一个基于springboo ...

  6. shell变量替换 SHELL字符串处理技巧(${}、##、%%)

      在SHELL编程中,经常要处理一些字符串变量.比如,计算长度啊.截取子串啊.字符替换啊等等,常常要用到awk.expr.sed.tr等命令.下面给大家介绍个简单的字符串处理方法,用不着嵌套复杂的子 ...

  7. sentinel控制台的使用

    一,下载sentinel控制台:sentinel-dashboard-1.7.0.jar , 注 1.7.1版本控制台与最新的sentinel有冲突,会报invalid type错误 二,启动sent ...

  8. ubuntu 使用cuda以及cudnn

    关于我自己的电脑是 mx250显卡. 使用cuda之前需要解决显卡驱动的问题,这个问题最简单的办法就是在[软件更新器]里面更换下载源.我的语言设置是中文,所以默认是cn的镜像源.更换成ustc(中国科 ...

  9. 分区表的表进行update操作

    今天对一张创建了分区表的表进行update操作,正好需要修改的是创建分区的那一列,由于是要修改在分区表范围内的数据,所以无法修改. 然后搜了一下,需要修改row movement这个属性:alter ...

  10. uart接口介绍和认识

    接口/总线/驱动 UART (Universal Asynchronous Receiver/Transmitter) 通用异步收发器. UART是用于控制计算机与串行设备的芯片.有一点要注意的是,它 ...