首页后端开发GOGo内存管理和分配策略

Go内存管理和分配策略

时间2023-04-16 21:27:01发布访客分类GO浏览562
导读:前言开始了解Go内存分配之前我们来简单了解下虚拟内存技术。虚拟内存技术物理内存:实际通过物理内存而获得的内存空间虚拟内存:与物理内存相反,是指根据系统需要从硬盘中虚拟的划出一部分存储空间而虚拟内存技术就是对内存的一种抽象,有了这层抽象之后,...

前言

开始了解Go内存分配之前我们来简单了解下虚拟内存技术。

虚拟内存技术

物理内存:实际通过物理内存而获得的内存空间

虚拟内存:与物理内存相反,是指根据系统需要从硬盘中虚拟的划出一部分存储空间

而虚拟内存技术就是对内存的一种抽象,有了这层抽象之后,程序运行进程的总大小可以超过实际可用的物理内存大小,每个进程都有自己的独立虚拟地址空间,然后通过CPU和MMU把虚拟内存地址转换为实际物理地址。

TCMalloc内存分配算法简述

TCMalloc全称是Thread Cache Malloc,是google为C语言开发的内存分配算法,是Go内存分配的起源。我们对它做个简单的了解,看看它的核心思想和几个重要概念,更能帮助我们理解Go内存分配和TCMalloc的相似和不同的地方。

核心思想

TCMalloc内存分配算法的核心思想是把内存分为多级管理,从而降低锁的粒度,它将可用的堆内存采用二级分配的方式进行管理,每个线程都会自行维护一个独立的线程内存池,进行内存分配时优先从该线程内存池中分配, 当线程内存池不足时才会向全局内存池申请,以避免不同线程对全局内存池的频繁竞争 ,进一步的降低了内存并发访问的粒度。

TCMalloc重要概念

  1. Page: 操作系统对内存的管理同样是以页为单位,但TCMalloc中的Page和操作系统的中页是倍数关系,x64下Page大小为8KB
  2. Span: 一组连续的Page被叫做Span,是TCMalloc内存管理的基本单位,有不同大小的Span,比如2个Page大的Span,16个Page大的Span
  3. ThreadCache: 每个线程各自的Cache,每个ThreadCache包含多个不同规格的Span链表,叫做SpanList, 内存分配的时候,可以根据要分配的内存大小,快速选择不同大小的SpanList,在SpanList上选择合适的Span,每个线程都有自己的ThreadCache,所以ThreadCache是无锁访问的
  4. CentralCache: 中心Cache,所有线程共享的Cache,也是保存的SpanList,数量和ThreadCache中数量相同 当ThreadCache中内存不足时,可以从CentralCache中获取 当ThreadCache中内存太多时,可以放回CentralCache 由于CentralCache是线程共享的,所以它的访问需要加锁
  5. PageHeap: 堆内存的抽象,同样当CentealCache中内存太多或太少时,都可从PageHeap中放回或获取,同样,PageHeap的访问也是需要加锁的

Go的内存分配算法是基于TCMalloc(Thread Cache malloc,线程缓存分配器)内存分配算法实现的,通过借鉴了TCmalloc的思想,开发出Go的内存分配器,核心实现在内置运行时(就是runtime)。

Go内存分配

核心思想

Go在程序启动的时候,会分配一块连续的内存(注意这时还只是一段虚拟的地址空间,并不会真正地分配内存),切成小块后自己进行管理,对内存的分配遵循以下思想。

  1. 每次从操作系统申请一大块内存, 以减少系统调用。
  2. 将申请到的大块内存按照特定大小预先切分成小块, 构成链表。
  3. 为对象分配内存时, 只需从大小合适的链表提取一个小块即可。
  4. 回收对象内存时, 将该小块内存重新归还到原链表, 以便复用。
  5. 如闲置内存过多, 则尝试归还部分内存给操作系统, 降低整体开销

内存管理图

先看图,我们先在脑中构造一个基础的概念图,然后再一个个解释,我觉得这种方式比只读枯燥的文字更有效。

我们从Go内存管理结构图中可以看出内存管理由mcache、mcentral、mheap组成一个三级管理结构,本质上都是对mspan的管理,三者之间没有严格的包含关系,只是用于不同的目的来共同配合管理所有mspan。

mspan其实就是Go中内存管理的基本单元,是由一片连续的 8kB 的页(page)组成的内存块。小对象和大对象分配的位置不用,大对象在mheap上分配,小对象使用mcache的tiny分配器分配。而文章开始我们为什么要去了解虚拟内存技术呢,可以看到mheap向操作系统申请新内存时,是向虚拟内存申请。

内存管理单元 mspan

Span是go内存管理的基本单位,代码中为mspan,一组连续的Page组成1个Span,所以上图一组连续的浅蓝色长方形代表的是一组Page组成的1个Span,另外,1个淡紫色长方形为1个Span。go把内存分为67个大小不同的span(SizeClass有67种),并且大小是不固定的。

sizeclasses.go对span数量67写死在代码中:

// src/runtime/mheap.go 的mspan结构体

type mSpanList struct {

 first *mspan // first span in list, or nil if none
 last  *mspan // last span in list, or nil if none
}


type mspan struct {

        next *mspan     // 链表后向指针,用于将span链接起来
        prev *mspan     // 链表前向指针,用于将span链接起来
        list *mSpanList // 双端队列的head

        startAddr uintptr // 起始地址,也即所管理页的地址
        npages    uintptr // 块个数,表示有多少个块可供分配
        ...
}
    

内存管理组件

内存管理器由mcache, mcentral, mheap3种组件构成: 三级管理结构是为了方便对span进行管理,加速对span对象的访问和分配,这三个结构在runtime中分别有对应的mcache.go、mcentral.go、mheap.go文件。对于如何实现申请、分配、释放内存的代码我们就不去做了解了,了解原理应付面试就够了。

  • mcache:保存的是各种大小的Span,并按Span class分类,小对象直接从mcache分配内存,它起到了缓存的作用,并且可以无锁访问 Go中是每个P拥有1个mcache,因为在Go程序中,当前最多有GOMAXPROCS个线程在运行,所以最多需要GOMAXPROCS个mcache就可以保证各线程对mcache的无锁访问
  • mcentral:是所有线程共享的缓存,需要加锁访问,它按Span class对Span分类,串联成链表,当mcache的某个级别Span的内存被分配光时,它会向mcentral申请1个当前级别的Span
  • mheap:是堆内存的抽象,把从OS(系统)申请出的内存页组织成Span,并保存起来。当mcentral的Span不够用时会向mheap申请,mheap的Span不够用时会向OS申请,向OS的内存申请是按页来的,然后把申请来的内存页生成Span组织起来,同样也是需要加锁访问的。 mheap主要用于大对象的内存分配,以及管理未切割的mspan,用于给mcentral切割成小对象

把这些概念结合起来,可以用下面图进行概述三者之间的联系和对mspan的不同处理。

分配流程

Go的内存分配器在分配对象时,根据对象的大小,分成三类:小对象(小于等于16B)、一般对象(大于16B,小于等于32KB)、大对象(大于32KB)。

大体上的分配流程:

  • 32KB 的对象,直接从mheap上分配;
  • =16B 的对象使用mcache的tiny分配器分配;
  • (16B,32KB] 的对象,首先计算对象的规格大小,然后使用mcache中相应规格大小的mspan分配;
  • 如果mcache没有相应规格大小的mspan,则向mcentral申请
  • 如果mcentral没有相应规格大小的mspan,则向mheap申请
  • 如果mheap中也没有合适大小的mspan,则向操作系统申请

声明:本文内容由网友自发贡献,本站不承担相应法律责任。对本内容有异议或投诉,请联系2913721942#qq.com核实处理,我们将尽快回复您,谢谢合作!

go

若转载请注明出处: Go内存管理和分配策略
本文地址: https://pptw.com/jishu/3402.html
Python 进阶指南(编程轻松进阶):五、发现代码异味 2023-04-09:使用 Golang 重写的 ffmpeg 示例encode_video.c,实现视频编码并将编码后的数据封装为容器格式,最终写入输出文件.

游客 回复需填写必要信息