前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >[译] Go语言内存管理与分配

[译] Go语言内存管理与分配

作者头像
猿哥
发布2019-11-10 16:38:48
6490
发布2019-11-10 16:38:48
举报
文章被收录于专栏:Web技术布道师Web技术布道师

本文基于Go 1.13

Go程序的内存从申请阶段到不再使用后的释放阶段都由Go标准库自动管理。尽管管理工作不需要开发者参与,但是Go对内存管理的底层实现做了非常好的优化,里面充满了有意思的知识点,还是值得我们学习的。

从堆上申请内存

Go内存管理的设计目标是在并发环境下保持高性能,并且集成垃圾回收器。让我们从一个简单的例子开始:

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

//go:noinline这行注释可以禁止编译时的内联优化,从而避免编译时把smallAllocation这个函数调用直接优化没了。

运行逃逸分析命令go tool compile "-m" main.go,得到内存申请情况:

main.go:14:9: &smallStruct literal escapes to heap

运行go tool compile -S main.go命令,获取程序的汇编代码,可以更清晰的查看内存申请情况:

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

newobject是用于申请内存的内建函数,newobjectmallocgc的代理,mallocgc是管理堆内存的函数。Go分配内存有两种策略:小块内存申请和大块内存申请。

小块内存申请

对于32KB以下的小块内存申请,Go会尝试从本地缓存mcache中获取内存。mcache包含了一系列被称为mspanspan列表,mspan包含了可供分配使用的内存:

Go的线程调度模型中,每个系统线程M和一个上下文P挂钩,在一个指定时间点最多只能处理一个协程G。申请内存时,当前协程会首先在所属M的本地缓存中的span列表中查找可用的内存块。使用本地缓存的好处是不用加锁,更高效。

span列表按大小被划分为大约70个等级,大小从8字节到32K字节不等,不同等级存储不同大小的内存块:

在我们前面的例子,结构体的大小为32字节,所以使用32字节的span

每个等级的span链表会存在两份:一个链表用于存储内部不包含指针的对象,另一个链表用于存储内部包含指针的对象。这么的好处是垃圾回收时更高效,因为不需要扫描不包含指针的那个span链表。

译者 yoko 注: 对英文原文做个补充。 每个mcache包含了2 * 67个链表(一个元素个数为2 * 67的数组,数组中的一个元素即为一个mspan链表)。 这里的67怎么来的呢,为什么不是1呢? 实际上每个mspan都各自管理了一大块内存块,而每个mspan又被切割成n个小内存块(object),object才是真正分配给用户使用的内存块。 那么问题来了,mspan按多大切割成object合适呢,太小可能不满足用户申请的大小,太大又造成浪费。 Go采取的策略是将32K大小以内的大小预定义了67个大小等级,每一个链表中的所有mspan都按该链表所设定的大小等级切割object。 这样,用户申请内存时,向上取最接近的大小等级,然后去对应的链表中的mspan获取可用的object。 英文原文关于这部分说的不太清楚,并且上面的两张图画得都不是太准确。实际上应该是一行可能有多个mspan,然后每个mspan内又可能包含多个object

现在,你可能会奇怪如果mcache上没有空闲的内存块可供分配该怎么办。Go另外还维护了全局的span列表,同样也按大小分成多个级别,叫做mcentralmcentral包含两种链表,一张包含空闲内存块,一张包含已使用内存块:

mcentral维护了两张span链表。一张链表为non-empty类型,包含了可供分配的span(由于一个span可能包含多个object,只要有一个或一个以上的object可供分配即表示该span可供分配),一张为empty类型,包含已分配完毕的span。当Go执行垃圾回收时,如果span中的内存块被标记为可供分配,span会重新加入到non-empty链表中。

mcentral获取span的流程图如下:

mcentral中也没有可供分配的span时,Go会从堆上申请新的span并将其放入mcentral中:

堆在必要时向操作系统申请内存。它会申请一块大内存,被称为arena,在64位系统下为64MB,其它大部分系统为4MB,申请的内存同样用span管理:

大块内存申请

Go申请大于32KB的大块内存不使用本地缓存策略,而是将大小取整到页大小整数倍后直接从堆上申请。

全局图

现在我们在一个较高层次上,对Go的内存分配有了一个大致了解。让我们将所有的组件集合到一起来绘制一张全局图:

设计灵感

Go内存分配器的设计基于TCMalloc,TCMalloc是由Google专门为并行环境优化的内存分配器。TCMalloc的文档很值得一读,在文档里你也能找到本文中讲解到的一些概念。

原文链接: https://pengrl.com/p/38720/ 原文出处: yoko blog (https://pengrl.com) 原文作者: yoko 版权声明: 本文欢迎任何形式转载,转载时完整保留本声明信息(包含原文链接、原文出处、原文作者、版权声明)即可。本文后续所有修改都会第一时间在原始地址更新。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-11-07,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 PHP技术大全 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 从堆上申请内存
  • 小块内存申请
  • 大块内存申请
  • 全局图
  • 设计灵感
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档