跳过正文

Golang之内存管理2

·63 字·1 分钟
Chuck Chan
作者
Chuck Chan
分享技术、思考与生活

Golang之内存管理
#

概述
#

Golang的内存分配借鉴了Google的TMalloc,其核心思想是内存池+多级对象管理能加快分配速度,降低资源竞争。

Golang内存模型层次结构
#

image

MCache
#

访问mcache依然不需要加锁而是直接访问,且MCache中依然保存各种大小的Span。MCache是与Golang协程调度模型GPM中的P所绑定,而不是和线程绑定。因为Golang调度的GPM模型,真正可运行的线程M的数量与P的数量一致,即GOMAXPROCS个,所以MCache与P进行绑定更能节省内存空间使用,可以保证每个G使用MCache时不需要加锁就可以获取到内存。

image (1)

MCentral
#

当MCache中某个Size Class对应的Span被一次次Object被上层取走后,如果出现当前Size Class的Span空缺情况,MCache则会向MCentral申请对应的Span。Goroutine、MCache、MCentral、MHeap互相交换的内存单位是不同,向Mcentral申请内存是需要加锁的。

image (3)

MCentral与MCache不同的是,每个级别保存的不是一个Span,而是一个Span List链表。MCentral的内部构造如图所示

image (2)

MHeap
#

MHeap的上游是MCentral,MCentral中的Span不够时会向MHeap申请。MHeap的下游是操作系统,MHeap的内存不够时会向操作系统的虚拟内存空间申请。访问MHeap获取内存依然是需要加锁的。MHeap是对内存块的管理对象,是通过Page为内存单元进行管理。那么用来详细管理每一系列Page的结构称之为一个HeapArena,它们的逻辑层级关系如图所示。

image (4)

内存分配
#

Golang将内存管理的对象根据其大小分为三种类型:Tiny对象、小对象、大对象

  • Tiny对象:申请的内存小于16byte
  • 小对象:申请的内存介于16byte - 32byte
  • 大对象:申请的内存大于32byte

Tiny对象内存的分配
#

Golang内存管理为什么需要一个Tiny这样的16B空间? 原因是因为如果协程逻辑层申请的内存空间小于等于8B,那么根据正常的Size Class匹配会匹配到Size Class = 1(对应Span Class = 2或3),所以像 int32、 byte、 bool 以及小字符串等经常使用的Tiny微小对象,也都会使用从Size Class = 1申请的这8B的空间。但是类似bool或者1个字节的byte,也都会各自独享这8B的空间,进而导致有一定的内存空间浪费。

image (5)

可以看出来这样当大量的使用微小对象可能会对Size Class = 1的Span造成大量的浪费。所以Golang内存管理决定尽量不使用Size Class = 1的Span,而是将申请的Object小于16B的申请统一归类为Tiny对象申请。具体的申请流程如图所示。

image (6)

小对象内存的分配
#

分配小对象的标准流程是按照Span Class规格匹配的。

image (8)

大对象内存的分配
#

小对象是在MCache中分配的,而大对象是直接从MHeap中分配。对于不满足MCache分配范围的对象,均是按照大对象分配流程处理。大对象分配流程是协程逻辑层直接向MHeap申请对象所需要的适当Pages,从而绕过从MCaceh到MCentral的繁琐申请内存流程,大对象的内存分配流程相对比较简单,具体的流程如图所示。

image (9)

总结
#

Golang的内存模型是基于分层缓存机制的,针对不同的内存场景采用不同的独特解决方式,提高局部性逻辑和细微粒度内存的复用率。