稀疏内存稀疏内存是 Go 语言在 1.11 中提出的方案,使用稀疏的内存布局不仅能移除堆大小的上限[^5],还能解决 C 和 Go 混合使用时的地址空间冲突问题[^6] 。不过因为基于稀疏内存的内存管理失去了内存的连续性这一假设,这也使内存管理变得更加复杂:
文章插图
heap-after-go-1-11
图 7-8 二维稀疏内存
如上图所示,运行时使用二维的 runtime.heapArena 数组管理所有的内存,每个单元都会管理 64MB 的内存空间:
type heapArena struct { bitmap [heapArenaBitmapBytes]byte spans [pagesPerArena]*mspan pageInUse [pagesPerArena / 8]uint8 pageMarks [pagesPerArena / 8]uint8 zeroedBase uintptr}
该结构体中的 bitmap 和 spans 与线性内存中的 bitmap 和 spans 区域一一对应,zeroedBase 字段指向了该结构体管理的内存的基地址 。这种设计将原有的连续大内存切分成稀疏的小内存,而用于管理这些内存的元信息也被切分成了小块 。不同平台和架构的二维数组大小可能完全不同,如果我们的 Go 语言服务在 linux 的 x86-64 架构上运行,二维数组的一维大小会是 1,而二维大小是 4,194,304,因为每一个指针占用 8 字节的内存空间,所以元信息的总大小为 32MB 。由于每个 runtime.heapArena 都会管理 64MB 的内存,整个堆区最多可以管理 256TB 的内存,这比之前的 512GB 多好几个数量级 。
Go 语言团队在 1.11 版本中通过以下几个提交将线性内存变成稀疏内存,移除了 512GB 的内存上限以及堆区内存连续性的假设:
- runtime: use sparse mAppings for the heap
- runtime: fix various contiguous bitmap assumptions
- runtime: make the heap bitmap sparse
- runtime: abstract remaining mheap.spans access
- runtime: make span map sparse
- runtime: eliminate most uses of mheap_.arena_*
- runtime: remove non-reserved heap logic
- runtime: move comment about address space sizes to malloc.go
地址空间因为所有的内存最终都是要从操作系统中申请的,所以 Go 语言的运行时构建了操作系统的内存管理抽象层,该抽象层将运行时管理的地址空间分成以下的四种状态[^8]:
状态解释None内存没有被保留或者映射,是地址空间的默认状态Reserved运行时持有该地址空间,但是访问该内存会导致错误Prepared内存被保留,一般没有对应的物理内存
访问该片内存的行为是未定义的
可以快速转换到 Ready 状态Ready可以被安全访问
表 7-2 地址空间的状态
每一个不同的操作系统都会包含一组特定的方法,这些方法可以让内存地址空间在不同的状态之间做出转换,我们可以通过下图了解不同状态之间的转换过程:
文章插图
memory-regions-states-and-transitions
图 7-9 地址空间的状态转换
运行时中包含多个操作系统对状态转换方法的实现,所有的实现都包含在以 mem_ 开头的文件中,本节将介绍 Linux 操作系统对上图中方法的实现:
- runtime.sysAlloc 会从操作系统中获取一大块可用的内存空间,可能为几百 KB 或者几 MB;
- runtime.sysFree 会在程序发生内存不足(Out-of Memory,OOM)时调用并无条件地返回内存;
- runtime.sysReserve 会保留操作系统中的一片内存区域,对这片内存的访问会触发异常;
- runtime.sysMap 保证内存区域可以快速转换至准备就绪;
- runtime.sysUsed 通知操作系统应用程序需要使用该内存区域,需要保证内存区域可以安全访问;
- runtime.sysUnused 通知操作系统虚拟内存对应的物理内存已经不再需要了,它可以重用物理内存;
- runtime.sysFault 将内存区域转换成保留状态,主要用于运行时的调试;
内存管理组件Go 语言的内存分配器包含内存管理单元、线程缓存、中心缓存和页堆几个重要组件,本节将介绍这几种最重要组件对应的数据结构 runtime.mspan、runtime.mcache、runtime.mcentral 和 runtime.mheap,我们会详细介绍它们在内存分配器中的作用以及实现 。
推荐阅读
- 茶艺享受的是时间,茶艺根据不同的原则和方法的分类
- 茶油的营养价值,长期吃茶油的坏处
- 茶具的选购之配套用具,茶具的奇思妙想
- 科学管理Linux系统中的组与组成员
- 大红袍的产地,名茶大红袍的产地是哪
- svchost占用内存过高是怎么回事
- 基于MEC的边缘CDN业务调度方案及测试分析
- 茶香面包的做法,红茶面包棒的做法
- JavaScript的Array.flat函数深入探讨
- 买电脑、DIY电脑,你必须了解的避坑技能