Go堆内存管理

发布于:2025-06-24 ⋅ 阅读:(18) ⋅ 点赞:(0)

# Go堆内存管理

1. Go内存模型层级结构

img

Golang内存管理模型与TCMalloc的设计极其相似。基本轮廓和概念也几乎相同,只是一些规则和流程存在差异。

2. Go内存管理的基本概念

Go内存管理的许多概念在TCMalloc中已经有了,含义是相同的,只是名字有一些变化。

2.1 Page

与TCMalloc中的Page相同,x64架构下1个Page的大小是8KB。Page表示Golang内存管理与虚拟内存交互内存的最小单元。操作系统虚拟内存对于Golang来说,依然是划分成等分的N个Page组成的一块大内存公共池。

2.2 mspan

与TCMalloc中的Span一致。mspan概念依然延续TCMalloc中的Span概念,在Golang中将Span的名称改为mspan,1个mspan为多个Page(go中为8KB的内存大小)。1个mspan对应1个或多个大小相同的object,mspan主要用于分配对象的区块,下图简单说明了Span的内部结构。

img

mspan结构体如下:


type mspan struct {

    next *mspan     // 在mspan链表中,指向后一个mspan

    prev *mspan     // 在mspan链表中,指向前一个mspan

    list *mSpanList // 供debug使用

    startAddr uintptr // mspan起始地址

    npages    uintptr // 当前mspan对应的page数

    manualFreeList gclinkptr // mSpanManual状态mspan中的可用对象链表

    // freeindex是slot索引,标记下一次分配对象时应该开始搜索的地址, 分配后freeindex会增加

    // 每一次分配都从freeindex开始扫描allocBits,直到它遇到一个表示空闲对象的0

    // 在freeindex之前的元素都是已分配的, 在freeindex之后的元素有可能已分配, 也有可能未分配

    freeindex uintptr

    nelems uintptr // 当前span中object数量.



    // allocCache是从freeindex位置开始的allocBits缓存

    allocCache uint64



    // allocBits用于标记哪些元素是已分配的, 哪些元素是未分配的。

    // 使用freeindex + allocBits可以在分配时跳过已分配的元素, 把对象设置在未分配的元素中.

    allocBits  *gcBits

    // 用于在gc时标记哪些对象存活, 每次gc以后allocBits都会与gcmarkBits保持一致

    gcmarkBits *gcBits



    // 清理代数,每GC1次sweepgen会+2

    // sweepgen=currrent sweepgen - 2:该span需要被清扫

    // sweepgen=currrent sweepgen - 1:该span正在被清扫

    // sweepgen=currrent sweepgen:该span已被清扫,带使用

    // sweepgen=currrent sweepgen + 1:该span在清扫开始前,仍然被缓存,需要被清扫

    // sweepgen=currrent sweepgen + 3:该span已被清扫,仍然被缓存

    sweepgen    uint32

    divMul      uint32        // for divide by elemsize

    allocCount  uint16        // 已分配对象的数量

    spanclass   spanClass

    state       mSpanStateBox

    needzero    uint8         // 在分配前需要清零

    elemsize    uintptr       // 对象大小

    limit       uintptr       // span数据末尾

    speciallock mutex         // specials链表的锁

    specials    *special      // 根据object偏移量排序的special链表.

}

mspan的allocBits是一个bitmap,用于标记哪些元素是已分配的, 哪些元素是未分配的。通过使用allocBits已经可以达到O(1)的分配速度,但是go为了极限性能,对其做了一个缓存allocCache,allocCache是从freeindex开始的allocBits缓存。

2.3 Size Class

Golang内存管理针对衡量内存的概念又更加详细了很多,这里面介绍一些基础的有关内存大小的名词及算法。

  1. Object Class是指协程应用逻辑一次向Go内存申请的对象Object大小。Object是Golang内存管理模块针对内存管理更加细化的内存管理单元。一个Span在初始化时会被分成多个Object。

    比如Object Size是8B(8字节)大小的Object,所属的Span大小是8KB(8192字节),那么这个Span就会被平均分割成1024(8192/8=1024)个Object。

    逻辑层从Golang内存模型取内存,实则是分配一个Object出去。为了更好的让读者理解,这里假设了几个数据来标识Object Size 和Span的关系 ,如下图所示。

    img

Page是Golang内存管理与操作系统交互时,衡量内存容量的基本单元

Object是用来存储一个变量数据的内存空间, 是Golang内存管理为对象分配存储内存的基本单元

  1. Size Class是指Object大小的级别。比如Object Size在1Byte~8Byte之间的Object属于Size Class 1级别,Object Size 在8B~16Byte之间的属于Size Class 2级别。本质上,golang的Size Class与TCMalloc中size class都是表示一块内存的所属规格。

go中共存在_NumSizeClasses = 68个Size Class(0~68),所以也对应着68个Object Class

  1. Span Class是Golang内存管理额外定义的规格属性,也是针对Object大小来进行划分的。但是为了优化GC Mark阶段,go内部让一个Size Class对应2个Span Class,其中一个Span为存放需要GC扫描的对象(包含指针的对象, scan span),另一个Span为存放不需要GC扫描的对象(不包含指针的对象, noscan span)。

通过设置两种span,让GC扫描对象的时候,对于noscan的span可以不去查看bitmap区域来标记子对象。也就是说进行扫描的时候,直接判定该span中的对象不会存在引用对象,不再进行更深层的扫描,这样可以大幅提升GC Mark的效率。

具体Span Class与Size Class的逻辑结构关系如下图所示。

img

其中Size Class和Span Class的对应关系计算方式可以参考Golang源代码,如下:


//usr/local/go/src/runtime/mheap.go



type spanClass uint8 



//……(省略部分代码)



func makeSpanClass(sizeclass uint8, noscan bool) spanClass {

   return spanClass(sizeclass<<1) | spanClass(bool2int(noscan))

}



//……(省略部分代码)

makeSpanClass()函数为通过Size Class来得到对应的Span Class,其中第二个形参noscan表示当前对象是否需要GC扫描

,不难看出来Span Class 和Size Class的对应关系公式如下表所示:

| 对象 | Size Class 与 Span Class对应公式 |

| ---------------------------- | -------------------------------- |

| 需要GC扫描是否存在引用对象 | Span Class = Size Class * 2 + 0 |

| 不需要GC扫描是否存在引用对象 | Span Class = Size Class * 2 + 1 |

Golang源码里列举了详细的Size Class和Object大小、存放Object数量,以及每个Size Class对应的Span内存大小关系,我们这里只展示部分:


//usr/local/go/src/runtime/sizeclasses.go



package runtime



// [class]: Size Class

// [bytes/obj]: Object Size,一次对外提供内存Object的大小

// [bytes/span]: 当前Object所对应Span的内存大小

// [objects]: 当前Span一共有多少个Object

// [tail waste]: 当前Span平均分N份Object后,会有多少内存浪费。 ===> [bytes/span]%[bytes/obj]

// [max waste]: 当前Size Class最大可能浪费的空间所占百分比。 ===> ((本级Object Size – (上级Object Size + 1))*本级Object数量) + [tail waste])/ 本级Span Size



// 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         32        8192      256           0        46.88%

//     4         48        8192      170          32        31.52%

//     5         64        8192      128           0        23.44%

//     6         80        8192      102          32        19.07%

//     7         96        8192       85          32        15.95%

//     8        112        8192       73          16        13.56%

//     9        128        8192       64           0        11.72%

//    10        144        8192       56         128        11.82%

//    ......

由以上源码可见, 并没有列举Size Class为0的规格刻度内存。对于Span Class为0和1的,也就是对应Size Class为0的规格刻度内存,mcache实际上是没有分配任何内存的。因为Golang内存管理对内存为0的数据申请做了特殊处理,如果申请的数据大小为0将直接返回一个固定内存地址,不会走Golang内存管理的正常逻辑,详见以下源码


//usr/local/go/src/runtime/malloc.go



// Al Allocate an object of size bytes.                                     

// Sm Small objects are allocated from the per-P cache's free lists.        

// La Large objects (> 32 kB) are allocated straight from the heap.         

func mallocgc(size uintptr, typ *_type, needzero bool) unsafe.Pointer {                        

// ……(省略部分代码)



if size == 0 {

return unsafe.Pointer(&zerobase)

}



//……(省略部分代码)

}

上述代码可以看见,如果申请的size为0,则直接return一个固定地址**zerobase**。所以在68种Size Class中,执行newobject时,会申请内存的Size Class为67种。在Golang中如[0]int、 struct{}所需要内存大小均是0,这也是为什么很多开发者在通过Channel做同步时,发送一个struct{}数据,因为不会申请任何内存,能够适当节省一部分内存空间。

golang中[0]int、 struct{}等,全部的0内存对象分配,返回的都是一个固定的地址。

max waste为当前Size Class最大可能浪费的空间所占百分比计算方式,详见下图

img

2.4 MCache

mcache与TCMalloc中的ThreadCache类似,但也有所不同。

相同点:都保存的是各种大小的Span,并按Span class分类,小对象直接从此分配内存,起到了缓存的作用,并且可以无锁访问

不同点:TCMalloc中是1个线程1个ThreadCache,Go中是1个P拥有1个mcache,两者绑定关系的区别如下图所示

img

如果将上图的mcache展开,来看mcache的内部构造,则具体的结构形式如下图6所示

img

当其中某个Span Class的MSpan已经没有可提供的Object时,MCache则会向MCentral申请一个对应的MSpan。mcache在初始化时是没有任何mspan资源的,在使用过程中会动态地申请,不断地去填充 alloc[numSpanClasses]*mspan,通过双向链表连接

下面具体看一下mcache在源码中的定义:


//go:notinheap

type mcache struct { 

   

   tiny             uintptr //<16byte 申请小对象的起始地址

   tinyoffset       uintptr //从起始地址tiny开始的偏移量

   local_tinyallocs uintptr //tiny对象分配的数量   



   alloc [numSpanClasses]*mspan // 分配的mspan list,其中numSpanClasses=67*2,索引是splanclassId



   stackcache [_NumStackOrders]stackfreelist //栈缓存



  

   local_largefree  uintptr                  // 大对象释放字节数

   local_nlargefree uintptr                  // 释放的大对象数量

   local_nsmallfree [_NumSizeClasses]uintptr // 每种规格小对象释放的个数



 

   flushGen uint32 //扫描计数

}

MCache中每个Span Class都只会对应一个MSpan对象,不同Span Class的MSpan的总体长度不同,参考runtime/sizeclasses.go的标准规定划分。比如对于Span Class为4的MSpan来说,存放内存大小为1Page,即8KB。每个对外提供的Object大小为16B,共存放512个Object。其他Span Class的存放方式类似。

通过源码可以看到MCache通过alloc[numSpanClasses]mspan管理了很多不同规格不同类型的span,golang对于*[16B,32KB]**的对象会使用这部分span进行内存分配,所有在这区间大小的对象都会从alloc这个数组里寻找。


var sizeclass uint8

//确定规格

if size <= smallSizeMax-8 {

   sizeclass = size_to_class8[(size+smallSizeDiv-1)/smallSizeDiv]

} else {

   sizeclass = size_to_class128[(size-smallSizeMax+largeSizeDiv-1)/largeSizeDiv]

}

size = uintptr(class_to_size[sizeclass])

spc := makeSpanClass(sizeclass, noscan)

//alloc中查到

span := c.alloc[spc]

而对于更小的对象,我们叫它tiny对象,golang会通过tiny和tinyoffset组合寻找位置分配内存空间,这样可以更好的节约空间,源码如下:


off := c.tinyoffset

//根据不同大小内存对齐

if size&7 == 0 {

   off = round(off, 8)

} else if size&3 == 0 {

   off = round(off, 4)

} else if size&1 == 0 {

   off = round(off, 2)

}

if off+size <= maxTinySize && c.tiny != 0 {

   // tiny+偏移量

   x = unsafe.Pointer(c.tiny + off)

   c.tinyoffset = off + size

   c.local_tinyallocs++

   mp.mallocing = 0

   releasem(mp)

   return x

}

// 空间不足从alloc重新申请空间用于tiny对象分配

span := c.alloc[tinySpanClass]

2.5 MCentral

MCentral与TCMalloc中的Central概念依然相似。向MCentral申请Span是同样是需要加锁的。

当MCache的某个级别Span的内存被分配光时,它会向MCentral申请1个当前级别的Span。

Goroutine、MCache、MCentral、MHeap互相交换的内存单位是不同,其中协程逻辑层与MCache的内存交换单位是Object,MCache与MCentral、MCentral与MHeap的内存交换单位是Span,MHeap与操作系统的内存交换单位是Page

MCentral与TCMalloc中的Central不同的是:CentralCache是每个级别的Span有1个链表,mcache是每个级别的Span有2个链表。如下图所示。

img

MCentral属于MHeap,MCentral是各个规格的mcentral集合,实际上1个mcentral对应1个Span Class,即Span Class个mcentral小内存管理单元。对应源码为:


type mheap struct {

    ......

    central [numSpanClasses]struct {

        mcentral mcentral

        pad      [cpu.CacheLinePadSize - unsafe.Sizeof(mcentral{})%cpu.CacheLinePadSize]byte

    }

    ......

}

  1. NonEmpty Span List

    表示还有可用空间的Span链表。链表中的所有Span都至少有1个空闲的Object空间。如果MCentral上游MCache退还Span,会将退还的Span加入到NonEmpty Span List链表中。

  2. Empty Span List

    表示没有可用空间的Span链表。该链表上的Span都不确定是否存在空闲的Object空间。如果MCentral提供给一个Span给到上游MCache,那么被提供的Span就会加入到Empty List链表中。

注意 在Golang 1.16版本之后,MCentral中的NonEmpty Span List 和 Empty Span List

均由链表管理改成集合管理,分别对应Partial Span Set 和 Full Span Set。虽然存储的数据结构有变化,但是基本的作用和职责没有区别。

下面是MCentral层级中其中一个Size Class级别的MCentral的定义Golang源代码(V1.14版本):


//usr/local/go/src/runtime/mcentral.go  , Go V1.14



// Central list of free objects of a given size.

// go:notinheap

type mcentral struct {

  lock      mutex      //申请MCentral内存分配时需要加的锁



  spanclass spanClass //当前哪个Size Class级别的



  // list of spans with a free object, ie a nonempty free list

  // 还有可用空间的Span 链表

  nonempty  mSpanList 



  // list of spans with no free objects (or cached in an mcache)

  // 没有可用空间的Span链表,或者当前链表里的Span已经交给mcache

  empty     mSpanList 



  // nmalloc is the cumulative count of objects allocated from

  // this mcentral, assuming all spans in mcaches are

  // fully-allocated. Written atomically, read under STW.

  // nmalloc是从该mcentral分配的对象的累积计数

  // 假设mcaches中的所有跨度都已完全分配。

  // 以原子方式书写,在STW下阅读。

  nmalloc uint64

}

在GolangV1.16版本的相关MCentral结构代码如下:


//usr/local/go/src/runtime/mcentral.go  , Go V1.16+



//…



type mcentral struct {

  // mcentral对应的spanClass

  spanclass spanClass



  partial  [2]spanSet // 维护全部空闲的Span集合

  full     [2]spanSet // 维护存在非空闲的Span集合

}



//…

新版本的改进是将List变成了两个Set集合,Partial集合与NonEmpty Span List责任类似,Full集合与Empty Span List责任类似。可以看见Partial和Full都是一个[2]spanSet类型,也就每个Partial和Full都各有两个spanSet集合,这是为了给GC垃圾回收来使用的,其中一个集合是已扫描的,另一个集合是未扫描的

2.6 MHeap

Golang内存管理的MHeap依然是继承TCMalloc的PageHeap设计。MHeap的上游是MCentral,MCentral中的Span不够时会向MHeap申请。MHeap的下游是操作系统,MHeap的内存不够时会向操作系统的虚拟内存空间申请。访问MHeap获取内存依然是需要加锁的。

MHeap是对内存块的管理对象,是通过Page为内存单元进行管理。那么用来详细管理每一系列Page的结构称之为一个HeapArena,它们的逻辑层级关系如下图所示。

img

一个HeapArena占用内存64MB,其中里面的内存的是一个一个的mspan,当然最小单元依然是Page,图中没有表示出mspan,因为多个连续的page就是一个mspan。所有的HeapArena组成的集合是一个arenas [1]*[4M]*heapArena数组,运行时使用arenas 管理所有的内存。

mheap是Golang进程全局唯一的,所以访问依然加锁。图中又出现了mcentral,因为mcentral本也属于mheap中的一部分。只不过会优先从MCentral获取内存,如果没有mcentral会从Arenas中的某个heapArena获取Page

heapArena结构体如下:


type heapArena struct {   

   bitmap [heapArenaBitmapBytes]byte  // 用于标记当前这个HeapArena的内存使用情况,1. 对应地址中是否存在过对象、对象中哪些地址包含指针,2. 是否被GC标记过。主要用于GC

   spans [pagesPerArena]*mspan  //  存放heapArena中的span指针地址

   pageInUse [pagesPerArena / 8]uint8   // 保存哪些spans处于mSpanInUse状态

   pageMarks [pagesPerArena / 8]uint8   // 保存哪些spans中包含被标记的对象

   pageSpecials [pagesPerArena / 8]uint8  // 保存哪些spans是特殊的

   checkmarks *checkmarksMap  // debug.gccheckmark state

   zeroedBase uintptr  //该arena第一页的第一个字节地址

}

根据heapArena结构体,我们可以了解到mheap内存空间的逻辑视图如下所示:

img

其中arena区域就是我们通常说的heap, go从heap分配的内存都在这个区域中。

其中spans区域用于表示arena区中的某一页(Page)属于哪个span,spans区域中一个指针(8 byte)对应了arena区域中的一页(在go中一页=8KB)。所以spans的大小是 512GB / 页大小(8KB) * 指针大小(8 byte) = 512MB。spans区域和arenas区域的对应关系如下图所示:

img

其中每个HeapArean包含一个bitmap,其作用是用于标记当前这个HeapArena的内存使用情况。

1个bitmap的逻辑结构图如下所示:

img

1个bitmap是8bit,每一个指针大小的内存都会有两个bit分别表示是否应该继续扫描和是否包含指针,这样1个byte就会对应arena区域的四个指针大小的内存。当前HeapArena中的所有Page均会被bitmap所标记,bitmap的主要作用是服务于GC垃圾回收模块。

bitmap中的byte和arena的对应关系从末尾开始, 也就是随着内存分配会向两边扩展

img

MHeap里面相关的数据结构和指针依赖关系,可以参考下图:

img

mheap结构体如下:


type mheap struct {

    lock  mutex    //必须在系统堆栈上获得,否则当G持有锁时,堆栈增长,可能会自我死锁

    pages pageAlloc // page分配器数据结构

    sweepgen     uint32 // 记录span的sweep及cache状态

    sweepDrained uint32 // 所有的span都已被清扫,或都正在被清扫

    sweepers     uint32 // 启动的swepper数量

    allspans []*mspan // 曾经创建的所有mspans地址的切片,allspans的内存是手动管理的,可以随着堆的增长而重新分配和移动。

                      // 一般来说,allspans受到mheap_.lock的保护,它可以防止并发访问以及释放后备存储。

                      // 在STW期间的访问可能不会持有锁,但必须确保访问周围不能发生分配(因为这可能会释放支持存储)。

    pagesInUse         uint64  // pages所属的spans处于状态mSpanInUse; 原子式更新

    pagesSwept         uint64  // 本周期内被清扫的pages数; 原子式更新

    pagesSweptBasis    uint64  // 被用作Proportional sweep模式原点的pagesSwept; 原子式更新

    sweepHeapLiveBasis uint64  // gcController.heapLive的值,作为扫描率的原点;带锁写入,不带锁读取。

    sweepPagesPerByte  float64 // Proportional sweep比例; 写时有锁,读时无锁

    // TODO(austin): pagesInUse should be a uintptr, but the 386 compiler can't 8-byte align fields.

    scavengeGoal uint64     // 维持的总的保留堆内存量(运行时试图通过向操作系统返回内存来维持该内存量,该内存量由heapRetained衡量)。

    reclaimIndex uint64 // 下一个要回收的page在allArenas中的索引

    reclaimCredit uintptr



    // arenas是*heapArena的map. 它指向整个可用的虚拟地址空间的每一个arena帧的堆的元数据。

    // 这是一个两级映射,由一个L1映射和可能的许多L2映射组成。当有大量的arena时,这可以节省空间

    arenas [1 << arenaL1Bits]*[1 << arenaL2Bits]*heapArena

    heapArenaAlloc linearAlloc // 用于分配heapArena对象的预留空间。这只在32位上使用,我们预先保留这个空间以避免与堆本身交错。

    arenaHints *arenaHint // arenaHints是一个地址列表,用于标记哪里的heap arenas需要扩容

    arena linearAlloc // 是一个预先保留的空间,用于分配heap arenas。只用在32位操作系统



    allArenas []arenaIdx // 所有arena序号集合,可以根据arenaIdx算出对应arenas中的那一个heapArena

    sweepArenas []arenaIdx // sweepArenas是在扫描周期开始时对所有Arenas的快照,通过禁用抢占可以安全读取

    markArenas []arenaIdx // markArenas是在标记周期开始时对所有Arenas的快照,由于allArenas只可向后追加,并且标记不会修改该切片内容,所以可以安全读取



    //curArena是堆当前正在扩容的区域,curArena总是与physPageSize对齐

    curArena struct {

        base, end uintptr

    }



    // central 是存放small size classes的列表

    central [numSpanClasses]struct {

        mcentral mcentral

        // pad确保mcentrals间隔CacheLinePadSize字节,以便每个mcentral.lock得到它自己的缓存行

        pad      [cpu.CacheLinePadSize - unsafe.Sizeof(mcentral{})%cpu.CacheLinePadSize]byte

    }



    spanalloc             fixalloc // allocator for span*

    cachealloc            fixalloc // allocator for mcache*

    specialfinalizeralloc fixalloc // allocator for specialfinalizer*

    specialprofilealloc   fixalloc // allocator for specialprofile*

    specialReachableAlloc fixalloc // allocator for specialReachable

    speciallock           mutex    // lock for special record allocators.

    arenaHintAlloc        fixalloc // allocator for arenaHints



    unused *specialfinalizer // never set, just here to force the specialfinalizer type into DWARF

}

arenaHint结构体为:


type arenaHint struct {

    addr uintptr // 为指向的对应heapArena首地址。

    down bool // 为当前的heapArena是否可以扩容。

    next *arenaHint // 指向下一个heapArena所对应的ArenaHint首地址。

}

3. 内存分配规则

介绍完内存管理基本概念,我们再来总结一下内存分配规则,流程图如下:

img

3.1 Tiny对象分配流程
  1. 判断对象大小是否小于maxSmallSize=32KB,如果小于32KB则进入Tiny对象或小对象申请流程,否则进入大对象申请流程。

  2. 判断对象大小是否小于maxTinySize=16B并且对象中是否包含指针,如果大于16B或包含指针,则进入小对象申请流程,否则进入Tiny对象申请流程

  3. Tiny对象申请流程后,会先获取mcache目前的tinyoffset,再根据申请tiny对象的大小及mcache.tinyoffset值,进行内存对齐,计算出满足内存对齐后的对象插入位置offset

  4. 如果从插入位置offset插入对象后,不超出16B,并且存在待分配的tiny空间,则将对象填充到该tiny空间,并将地址返回给M,结束内存申请

  5. 如果当前的tiny空间不足,则通过nextFreeFast(span)查找span中一个可用对象地址,存在则返回地址,并结束内存申请

  6. 如果span中不存在一个可用对象,则调用mcache.nextFree(tinySpanClass)从mcentral申请1个相同规格的msapn。申请成功则结束流程

3.2 小对象分配流程
  1. 进入小对象申请流程后,通过mcache.alloc(spc)获取1个指定规格的mspan

  2. 通过nextFreeFast(span)查找span中一个可用对象地址,存在则返回地址给协程逻辑层P,P得到内存空间,流程结束

  3. 如果不存在可用对象,则通过mcache.nextFree(tinySpanClass)中mcache.refill(spc)从mcentral申请1个相同规格的msapn

    4.mcache.refill(spc)中,会首先尝试通过mcentral的noempty list获取mspan,获取不到则在尝试通过mcentral的empty list获取mspan(1.16之后,通过mcentral.cacheSpan()从partial set获取mspan,获取不到则从full set获取可回收的mspan)。mcache成功获取mcentral返回的mspan后,返回可用对象地址,结束申请流程

  4. mcache中empty List(1.16之后,full set)也没有可回收的mspan,则会调用mcache.grow()函数,从mheap中申请内存

  5. mheap收到内存请求从其中一个heapArena从取出一部分pages返回给mcentral;当mheap没有足够的内存时,mheap会向操作系统申请内存,将申请的内存也保存到heapArena中的mspan中。mcentral将从mheap获取的由Pages组成的mspan添加到对应的span class链表或集合中

  6. 最后协程业务逻辑层得到该对象申请到的内存,流程结束

3.3 大对象分配流程
  1. 进入大对象分配流程后,会调用mcache.allocLarge()方法申请大对象

  2. mcache.allocLarge()中主要的mspan申请链路为:mheap.alloc -> mheap.allocSpan,mheap.allocSpan为申请mspan的核心方法。mheap.allocSpan会首先判断申请的page数是否小于P.pageCache的最大page数,如果P.pageCache满足需要,则会从P.mspancache获取mspan地址给P,流程结束

  3. P.pageCache不足,则对mheap加锁,从mheap.pageAlloc这种Radix tree(基数树)数据结构中查找可用的page,协程逻辑层P得到内存,流程结束

  4. mheap.pageAlloc中查找不存在可用的page,则调用mheap.grow()向操作系统申请内存。申请成功后,再次从mheap.pageAlloc中查找可以page,P得到内存后,流程结束


网站公告

今日签到

点亮在社区的每一天
去签到