Golang--内存管理

堆内存管理介绍

简单的内存管理思路

堆内存管理主要就是做三件事:分配内存块、组织内存块、回收内存块。

分配

每次申请堆内存都从未分配内存中分割出一个小内存块,然后将所有内存块使用链表组织起来。同时需要一些信息来描述每个内存块基本信息,如:大小、是否被使用、下一个内存块地址等...

一个内存块包含3类信息:元数据、用户数据、对齐字段。

释放

把需要释放的内存块从链表中取出来,标记为未使用。当下次需要分配内存块时,可以从未使用的内存块中先查找大小相近的内存块,若找不到再从未分配内存中分配内存。

但是这样的简单的设计随着内存不断的申请和释放,内存会存在大量的内存碎片,降低内存的使用率。

TCMalloc

Linux中有不少的内存管理库,如glibc的ptmalloc、FreeBSD的jemalloc、Google的tcmalloc等。其本质都是为了在再多线程编程下,达到更高的内存管理效率,更快地分配内存。

同一进程的所有线程共享相同的地址空间,它们申请内存时需要加锁,如果不加锁就会出现同一块内存被2个线程同时访问的问题。

TCMalloc的做法是为每一个线程预先分配一块缓存,线程申请小内存时,可以从缓存中分配内存。优点:

  1. 线程预分配缓存只需要一次系统调用,而线程从预分配的缓存中申请小内存时,都是用户态执行,不需要系统调用。这样缩短了内存总体的分配和释放时间。
  2. 多线程同时申请小内存,从各自的缓存分配,访问的是不同的地址空间,不需要加锁。降低了内存并发访问的时间。

基本原理

  • <font style="color:rgb(0, 0, 0);background-color:rgba(0, 0, 0, 0.06);">Page</font>:TCMalloc管理内存的基本单位,x64下一个Page默认8KB
  • <font style="color:rgb(0, 0, 0);background-color:rgba(0, 0, 0, 0.06);">Span</font>:一个或多个连续的Page组成一个Span。TCMalloc以Span为单位向系统申请内存。
  • <font style="color:rgb(0, 0, 0);background-color:rgba(0, 0, 0, 0.06);">TreadCache</font>:每个线程各自的缓存。一个<font style="color:rgb(0, 0, 0);background-color:rgba(0, 0, 0, 0.06);">TreadCache</font>中包含了多个不同级别的空闲内存块链表,同一链表的内存块大小相同,不同链表的内存块大小不同。这样可以在申请内存时,根据申请的内存大小,快速从合适的链表选择空闲内存块。
  • <font style="color:rgb(0, 0, 0);background-color:rgba(0, 0, 0, 0.06);">CentralCache</font>:所有线程共享的缓存,故它的访问需要加锁。其中结构与ThreadCache一致,当ThreadCache的内存块不足时,可以从<font style="color:rgb(0, 0, 0);background-color:rgba(0, 0, 0, 0.06);">CentralCache</font>取;ThreadCache内存块过多时,也可以放回<font style="color:rgb(0, 0, 0);background-color:rgba(0, 0, 0, 0.06);">CentralCache</font>
  • <font style="color:rgb(0, 0, 0);background-color:rgba(0, 0, 0, 0.06);">PageHeap</font>:堆内存的抽象,把从OS申请出的内存页组织成Span,并保存起来。PageHeap存有多个大小的Span链表。当CentralCache没有内存时,会从PageHeap取,再把1个Span拆分成多个内存块,添加到对应大小的链表中;当CentralCache内存过多时,会放回PageHeap。

不同大小对象分配流程

  • 小对象:0-256KB。

ThreadCache -> CentralCache -> HeapPage,大部分时候,ThreadCache缓存都是足够的,不需要去访问CentralCache和HeapPage,无锁分配加无系统调用,分配效率是非常高的。

  • 中对象:257KB-1MB

直接从PageHeap选择合适的大小的Span即可。

  • 大对象:>1MB

从large span set中选择合适数量的页面组成Span。

Go内存管理

Go的内存管理借鉴了TCMalloc,Go内存管理的许多概念在TCMalloc中已经有了,含义是相同的,只是名字有一些变化。上图基于Go1.11,不同版本Go的内存管理有差异。

Page

与TCMalloc的Page一致,默认8KB

mspan

功能与TCMalloc中的Span类似,由N个连续的页组成,大小为Page大小*N,是Go内存管理的基本单位。

一个mspan会被分为一个个更小粒度的object,至于mspan的object有多大,由该mspan的sizeclass决定。

一个mspan有几个重要的数值概念:

  • <font style="color:rgb(33, 37, 41);background-color:rgba(0, 0, 0, 0.06);">object size</font>:该mspan中一个object的大小
  • <font style="color:rgb(33, 37, 41);background-color:rgba(0, 0, 0, 0.06);">size class</font>:根据mspan的大小、mspan中一个object的大小而分出的不同类别。mspan有68种size class:0表示>32KB的mspan;1-67表示8B-32KB大小的mspan。
go 复制代码
字段解释:
class: sizeclass值 
bytes/obj: 该`mspan`拆分object大小
bytes/span: 该`mspan`的大小
objects: 该`mspan`共计包含的object数量
tail waste: 该`mspan`拆分为object之后,mspan剩余末尾浪费的内存

// class  bytes/obj  bytes/span  objects     tail waste  max waste
//
//     1          8        8192     1024           0     87.50%
//     2         16        8192      512           0     43.75%
//     3         24        8192      341           8     29.24%
//     4         32        8192      256           0     21.88%
//     5         48        8192      170          32     31.52%
//     6         64        8192      128           0     23.44%
// 略...
//    62      20480       40960        2           0      6.87%
//    63      21760       65536        3         256      6.25%
//    64      24576       24576        1           0     11.45%
//    65      27264       81920        3         128     10.00%
//    66      28672       57344        2           0      4.91%
//    67      32768       32768        1           0     12.50%
  • <font style="color:rgb(0, 0, 0);background-color:rgba(0, 0, 0, 0.06);">span class</font>

因为不包含指针对象的mspan无需GC扫描,根据mspan是否存放包含指针的对象,又可分为2类。

故,一共有68*2=136种不同的类别的mspan。

mspan的类别由其属性中的spanclass表示:

go 复制代码
// src/runtime/mheap.go:mspan
type mspan struct{
    ...
    spanclass spanClass//spanClass实际是uint8,一共8位
    ...
}

spanclass的前7位存放size class的值(0-67):。最后一位表示是否需要GC扫描。

mcache

go 复制代码
// src/runtime/mcache.go:mcache
type mcache struct{
    ...
    tiny  uintptr   //大小16B,用于分配小于16B的noscan类型
    alloc [numSpanClasses]*mspan //136种mspan,每种一个列表   
    ...
}

功能与TCMalloc的ThreadCache类似。

不同点在于:

TCMalloc中每1个线程1个ThreadCache,Go中是每1个P拥有1个mcache。因为最多有GOMAXPROCS个线程在用户态运行,线程的运行又是与P绑定的。最多只需要GOMAXPROCS个mcache就可以保证各个线程对mcache的无锁访问。

central

central是一个68*2=136长的mcentral列表,包含在mheap中:

go 复制代码
// src/runtime/mheap.go:mheap
type mheap struct{
    ...
    central [numSpanClasses]struct {
        mcentral mcentral
        pad      [(cpu.CacheLinePadSize - unsafe.Sizeof(mcentral{})%cpu.CacheLinePadSize) % cpu.CacheLinePadSize]byte
    }
    ...
}

其中mcentral:

go 复制代码
// src/runtime/mcentral.go:mcentral
type mcentral struct {
    ...
	spanclass spanClass  //存放的mspan类别
    partial [2]spanSet // list of spans with a free object,还没用尽的mspan组成的集合
	full    [2]spanSet // list of spans with no free objects,已经用尽的mspan组成的集合
    ...
}

central功能与TCMalloc中的CentralCache类似,不同点在于:

CentralCache是每个类别的Span有1个链表,而mcentral是每个类别的mspan有2个集合。

mheap

mheap与TCMalloc中的PageHeap类似,是整个堆内存的抽象。

Go将堆地址空间划分成了一个个<font style="color:rgb(0, 0, 0);background-color:rgba(0, 0, 0, 0.06);">arena</font>,64位机器上每个arena大小64MB,即8192个page大小。

一个arena对应一个heaparena结构,heaparena中包含了arena的各种元数据。

Go内存分配

对象类型

小对象都在mcache中分配,大对象直接从mheap中分配。

小对象内存分配

为对象寻找mspan

寻找步骤:

  1. 计算对象的内存大小size
  2. 按照size->size class的映射,得到sizeclass
  3. 再根据对象是否含有指针,得到span class
  4. 从mcache的alloc[span class]链表中查询可用的mspan

从mspan分配对象空间

mspan中的对象内存块,有的被占用,有的未占用。

分配内存时,要找到第一个可用的对象内存块,然后根据该mspan的起始地址计算出该对象内存块的内存地址。

若mcache中该类的mspan都已经满了,mcache就像向central申请一个mspan,拿到mspan后继续分配对象。

mcentral向mcache提供mspan

mcentral会从partial set中分配一个mspan给mcache;mcache中已经填满的mspan也会放入到mcentral的full set中。

若mcentral中也没有mspan了,mcentral会提供需要的page数和span class向mheap申请mspan;mheap没有足够内存则会向OS申请内存页,把申请到的内存页保存到mspan中。

大对象分配

大对象分配则和mcentral向mheap申请内存基本相同。

相关推荐
SuperEugene1 小时前
Vue3 + Element Plus 表格实战:批量操作、行内编辑、跨页选中逻辑统一|表单与表格规范篇
开发语言·前端·javascript
2501_924952691 小时前
C++模块化编程指南
开发语言·c++·算法
qzhqbb1 小时前
差分隐私与大模型+差分隐私在相关领域应用的论文总结
人工智能·算法
2401_831920741 小时前
基于C++的爬虫框架
开发语言·c++·算法
1104.北光c°1 小时前
深入浅出 Elasticsearch:从搜索框到精准排序的架构实战
java·开发语言·elasticsearch·缓存·架构·全文检索·es
MSTcheng.1 小时前
【优选算法必修篇——位运算】『面试题 01.01. 判定字符是否唯一&面试题 17.19. 消失的两个数字』
java·算法·面试
weixin_421922691 小时前
模板元编程性能分析
开发语言·c++·算法
SmartBrain1 小时前
Spring Boot的高性能技术栈的工程实践
spring boot·后端·架构
2401_851272991 小时前
C++中的类型擦除技术
开发语言·c++·算法
左左右右左右摇晃1 小时前
Java并发——并发编程底层原理
java·开发语言