Go语言的内存管理

Go的内存管理基于内存池,包括mheap、mcentral和mcache等组件。内存池在启动时从操作系统申请大块内存,按page和span进行管理,使用sizeclass优化对象分配。mcache提供无锁分配,提高效率,而mcentral协调不同大小对象的分配。这种方式减少了内核态切换,降低了内存碎片,并提升了CPU缓存命中率。
摘要由CSDN通过智能技术生成

go 的内存管理原理

参考tcmalloc来进行,本质上就是一个内存池,只不过内部做了许多优化,比如自动伸缩内存池大小,合理的切割内存块等

内存池 mheap

Go程序在启动之初,会一次性从操作系统那里申请一大块内存作为内存池。这块内存空间会放在一个叫做mheap的struct中管理,mheap负责将这一整块内存切割成不同的区域,并将其中一部分内存切割成合适的大小,分配给用户使用。

page: 内存页,一块8k大小的内存空间。Go与操作系统之间的内存申请和释放,都是以page为单位的。

span:内存块,一个或多个连续的page组成一个span。

sizeclass: 空间规格,每个span都带有一个sizeclass,标记着该span中的page应该如何使用。

object:对象,用来存储一个变量数据内存空间,一个span在初始化时,会被切割成一堆等大的object。假设object的大小是16B,span的大小为8K,那么就会把span中的page初始化为512个object。所谓内存分配,就是分配一个object。

img

不同的颜色代表不同的span,不同的span的sizeclass不同,表示里面的page将会按照不同的规格切割成等大object用作分配。

内部的整体布局:

img

mheap.spans:用存储page和span信息,比如一个span的起始地址是多少,由几个page,已经使用了多少等。

mheap.bitmap: 存储着各个span中对象的标记信息,比如对象是否可以回收等。

mheap.arena_start:将要分配给应用程序使用的空间。

图中的空间大小,是 Go 向操作系统申请的虚拟内存地址空间,操作系统会将该段地址空间预留出来不做它用;而不是真的创建出这么大的虚拟内存,在页表中创建出这么大的映射关系。

mcentral

用途相同的span会以链表的形式组织在一起。这里的用途用sizeclass表示,就是指该span用来存储哪种大小的对象。比如分配一块大小为n的内存时,系统计算n应该使用哪种sizeclass,然后根据sizeclass的值去找到一个可用的span来用作分配。

img

找到合适的span后,会从中取出一个object返回给上层使用,这些span被放在一个叫做mcentrral的结构中管理。

mheap将从OS那里申请过来的内从初始化为一个大span(sizeclass = 0),然后根据需要从这个大span中切出小span,放在mcentral中管理。大span由mheap.freelarge和mheap.busylarge等进行管理。如果mecntral中的span不够用了,会从mheap.freelarge上再切一块,如果mheap.freelarge空间不够,会再次从OS那里申请内存。

这种方式可以避免出现内存碎片,因为同一个span时按照固定大小分配和回收的,不会出现不可利用的一小块内存把内存分割。

mcache

每一个cache和每一个处理器§是一一对应的,也就是说每一个P都有一个mcache成员。Goroutine申请内存时,首先从其所在的P的mcache中分配,如果mcache没有可用的span,再从mcentral中获取,并填充到mcache中

从mcache上分配内存空间不需要加锁,因为在同一时间,一个P只有一个线程在上边运行,不可能出现竞争。

img

zero size

对于一些所需内存大小为0的对象,比如[0]int,struct{}等本就不需要分配内存,所以系统会之间返回一个固定的内存地址

func mallocgc(size uintptr, typ *_type, flags uint32) unsafe.Pointer {
    // 申请的 0 大小空间的内存
    if size == 0 {
        return unsafe.Pointer(&zerobase)
    }
    //.....
}

总结

1、内存分配大多时候都是在用户态完成的,不需要频繁进入内核态。

2、每个 P 都有独立的 span cache,多个 CPU 不会并发读写同一块内存,进而减少 CPU L1 cache 的 cacheline 出现 dirty 情况,增大 cpu cache 命中率。

3、内存碎片的问题,Go 是自己在用户态管理的,在 OS 层面看是没有碎片的,使得操作系统层面对碎片的管理压力也会降低。

4、mcache 的存在使得内存分配不需要加锁。

当然这不是没有代价的,Go 需要预申请大块内存,这必然会出现一定的浪费,不过好在现在内存比较廉价,不用太在意。

总体上来看,Go 内存管理也是一个金字塔结构:

img

将有限的计算资源布局成金字塔结构,再将数据从热到冷分为几个层级,放置在金字塔结构上。调度器不断做调整,将热数据放在金字塔顶层,冷数据放在金字塔底层。这种设计利用了计算的局部性特征,认为冷热数据的交替是缓慢的。所以最怕的就是,数据访问出现冷热骤变。在操作系统上我们称这种现象为内存颠簸,系统架构上通常被说成是缓存穿透。其实都是一个意思,就是过度的使用了金字塔低端的资源。

参考:
https://cloud.tencent.com/developer/article/1422392

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值