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的allocspan 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申请内存基本相同。

相关推荐
GetcharZp7 小时前
玩转 Linux 机器视觉:手把手带你搞定 Ubuntu 下海康工业相机 C++ SDK
后端
星星在线11 小时前
MusicFree:一个「All in One」的个人音乐服务器,让听歌回归简单
前端·后端
IT_陈寒12 小时前
Redis的SETNX并发问题让我加了三天班
前端·人工智能·后端
demo007x12 小时前
Docling 文档转换以及技术架构分析
前端·后端·程序员
袋鱼不重13 小时前
我的神奇同事,AI 用多了居然写了个 Open In Codex
前端·后端·ai编程
用户83562907805113 小时前
使用 Python 操作 Word 内容控件
后端·python
像我这样帅的人丶你还13 小时前
啥? 前端也要会干Java?🛵🛵🛵
后端
Hommy8813 小时前
【剪映小助手】添加贴纸接口(Add Sticker)
后端·github·剪映小助手·视频剪辑自动化·剪映api
LDR00613 小时前
Type-C 快充全面升级!LDR6601 赋能个人护理便携电机,重塑剃须刀 / 理发器新体验
c语言·开发语言
雪碧聊技术13 小时前
Tree.js是什么?一文讲透
开发语言·javascript·ecmascript