Go堆内存管理

# 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链表中,指向后一个mspanprev *mspan     // 在mspan链表中,指向前一个mspanlist *mSpanList // 供debug使用startAddr uintptr // mspan起始地址npages    uintptr // 当前mspan对应的page数manualFreeList gclinkptr // mSpanManual状态mspan中的可用对象链表// freeindex是slot索引,标记下一次分配对象时应该开始搜索的地址, 分配后freeindex会增加// 每一次分配都从freeindex开始扫描allocBits,直到它遇到一个表示空闲对象的0// 在freeindex之前的元素都是已分配的, 在freeindex之后的元素有可能已分配, 也有可能未分配freeindex uintptrnelems 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    uint32divMul      uint32        // for divide by elemsizeallocCount  uint16        // 已分配对象的数量spanclass   spanClassstate       mSpanStateBoxneedzero    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.gotype 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.gopackage 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:notinheaptype mcache struct { tiny             uintptr //<16byte 申请小对象的起始地址tinyoffset       uintptr //从起始地址tiny开始的偏移量local_tinyallocs uintptr //tiny对象分配的数量   alloc [numSpanClasses]*mspan // 分配的mspan list,其中numSpanClasses=67*2,索引是splanclassIdstackcache [_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 + sizec.local_tinyallocs++mp.mallocing = 0releasem(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 mcentralpad      [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:notinheaptype 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已经交给mcacheempty     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对应的spanClassspanclass spanClasspartial  [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标记过。主要用于GCspans [pagesPerArena]*mspan  //  存放heapArena中的span指针地址pageInUse [pagesPerArena / 8]uint8   // 保存哪些spans处于mSpanInUse状态pageMarks [pagesPerArena / 8]uint8   // 保存哪些spans中包含被标记的对象pageSpecials [pagesPerArena / 8]uint8  // 保存哪些spans是特殊的checkmarks *checkmarksMap  // debug.gccheckmark statezeroedBase 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]*heapArenaheapArenaAlloc linearAlloc // 用于分配heapArena对象的预留空间。这只在32位上使用,我们预先保留这个空间以避免与堆本身交错。arenaHints *arenaHint // arenaHints是一个地址列表,用于标记哪里的heap arenas需要扩容arena linearAlloc // 是一个预先保留的空间,用于分配heap arenas。只用在32位操作系统allArenas []arenaIdx // 所有arena序号集合,可以根据arenaIdx算出对应arenas中的那一个heapArenasweepArenas []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 specialReachablespeciallock           mutex    // lock for special record allocators.arenaHintAlloc        fixalloc // allocator for arenaHintsunused *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得到内存后,流程结束

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/news/910438.shtml
繁体地址,请注明出处:http://hk.pswp.cn/news/910438.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

零售 EDI:Chewy EDI 项目注意事项

在此前的文章《供应商对接Chewy的EDI需求》中&#xff0c;介绍了Chewy的EDI需求&#xff0c;本文主要为大家分享Chewy对于各个业务单据的细节性需求&#xff0c;了解这些细节性注意事项将帮助企业快速基于知行软件提供的EDI服务与Chewy建立EDI对接。 基于知行之桥EDI系统能够通…

Android录制视频自带铺满多行水印

文章目录 引言环境要求代码实现总结 引言 之前做过几种水印需求&#xff0c;这篇文章是关于使用Android原生库开发录制视频自带满帧文字水印。 环境要求 Android 7.0以上Android Studio &#xff0c;官方开发者官网视频录制功能参考开源库PictureSelector的camerax库 //用到的…

观远ChatBI:加速零售消费企业数据驱动的敏捷决策

近年来&#xff0c;随着国产大模型&#xff08;如DeepSeek&#xff09;的快速发展&#xff0c;企业对智能化数据分析工具的需求日益增长。观远数据推出的ChatBI&#xff0c;基于大语言模型&#xff08;LLM&#xff09;打造&#xff0c;旨在通过自然语言交互降低数据分析门槛&am…

鸿蒙NEXT-鸿蒙三层架构搭建,嵌入HMRouter,实现便捷跳转,新手攻略。(1/3)

接下来&#xff0c;我将手把手带领大家去完善&#xff0c;搭建一个鸿蒙的三层架构&#xff0c;另实现HMRouter的嵌入。完成后&#xff0c;大家可任意跳转页面&#xff0c;在三层架构中&#xff0c;书写属于自己的篇章。 第0步&#xff0c;项目与AGC华为控制台关联起来 首先AG…

鸿蒙ArkTs仿网易云音乐项目:架构剖析与功能展示

鸿蒙ArkTs仿网易云音乐项目&#xff1a;架构剖析与功能展示 一、引言 在移动应用开发的浪潮中&#xff0c;音乐类应用始终占据着重要的一席之地。网易云音乐凭借其丰富的音乐资源、个性化的推荐算法和独特的社交互动功能&#xff0c;深受广大用户的喜爱。本文将详细介绍一个基…

【web 安全】从 HTTP 无状态到现代身份验证机制

文章目录 Web 安全与系统设计Web存在的问题&#xff1a;Web 是无状态的解决方案一、早期解决方案&#xff1a;Session Cookie 的诞生二、第二阶段&#xff1a;Token 的出现&#xff08;前后端分离 移动端的解决方案&#xff09;三、分析总结&#xff1a;1.早期版本&#xff1…

FlutterUnit TolyUI | 布局游乐场

FlutterUnit 基于 TolyUI 大大简化了界面构建的代码复杂程度&#xff0c;因此之前想要实现的一些小功能&#xff0c;就可以轻松支持。布局游乐场是通过交互的方式来 直观体验 组件的布局特性&#xff0c;从而更易学和掌握。目前 FlutterUnit 已在 知识集录模块新增了 布局宝库&…

【数据分析一:Data Collection】信息检索

本节内容含有各典型数据集的推荐&#xff0c;以及其网址&#xff0c;大家根据需要自取 一、检索 最简单、最灵活的数据获取方式就是依靠检索&#xff1a; Google&#xff1a;更适合搜索英文信息 Google Dataset Search&#xff08;Google 数据集搜索&#xff09; 网址&…

23.ssr和csr的对比?如何依赖node.js实现

1.为什么说ssr 的node中间层请求速度快。相当于内网&#xff1f; 那vue.js加载怎么没有ssr和csr的说法啊 第一问&#xff1a;为什么说 SSR 的 Node 中间层请求速度快&#xff1f;是不是相当于内网&#xff1f; ✅ 是的&#xff0c;本质上就是「内网请求」&#xff0c;所以更快…

力扣刷题(第六十四天)

灵感来源 - 保持更新&#xff0c;努力学习 - python脚本学习 第一个错误的版本 解题思路 初始化左右边界&#xff1a;左边界 left 1&#xff0c;右边界 right n。二分查找循环&#xff1a; 计算中间版本号 mid。若 mid 是错误版本&#xff0c;说明第一个错误版本在 [le…

【图像处理入门】11. 深度学习初探:从CNN到GAN的视觉智能之旅

摘要 深度学习为图像处理注入了革命性动力。本文将系统讲解卷积神经网络(CNN)的核心原理,通过PyTorch实现图像分类实战;深入解析迁移学习的高效应用策略,利用预训练模型提升自定义任务性能;最后揭开生成对抗网络(GAN)的神秘面纱,展示图像生成与增强的前沿技术。结合代…

C++法则4: 如果一个构造函数的第一个参数是自身类类型的引用,且任何额外参数都有默认值,则此构造函数是拷贝构造函数。

C法则4&#xff1a; 如果一个构造函数的第一个参数是自身类类型的引用&#xff0c;且任何额外参数都有默认值&#xff0c;则此构造函数是拷贝构造函数。 拷贝构造函数的定义&#xff1a; 第一个参数是自身类类型的引用&#xff1a; 必须是引用&#xff08;通常为const引用&…

从头搭建环境安装k8s遇到的问题

基本信息 master节点IP&#xff1a; 172.31.0.3 node01节点IP&#xff1a;172.31.0.4 node02节点IP&#xff1a;172.31.0.5 子网掩码&#xff1a;255.255.0.0 网关&#xff1a;172.31.0.2 DNS:114.114.114.114 安装前要检查的信息 检查三台主机的mac地址是否重复&#xff1a…

Flask入门指南:从零构建Python微服务

1. Flask 是什么&#xff1f; Flask 是一个 微框架&#xff08;Microframework&#xff09;&#xff0c;特点包括&#xff1a; 轻量灵活&#xff1a;核心仅包含路由和模板引擎&#xff0c;其他功能通过扩展实现易于学习&#xff1a;代码直观&#xff0c;适合快速开发小型应用…

【LINUX网络】网络socet接口的基本使用以及实现简易UDP通信

根据本系列上两篇关于网络的初识介绍&#xff0c;现在我们开始实现一个UDP接口&#xff0c;以加强对该接口的理解。 1 . 服务器端 在本篇中&#xff0c;主要按照下面内容来实现&#xff1a; 创建并封装服务端&#xff1a;了解创建服务端的基本步骤 创建并封装客户端&#xff0…

MySQL的索引事务

索引 是什么 类似于目录&#xff0c;提高查询的速度&#xff0c;但是本身会占用空间&#xff0c;增删数据的时候也需要维护索引。所以查询操作频繁的时候可以创建索引。如果非条件查询列&#xff0c;或经常做插入、修改操作&#xff0c;或磁盘空间不足时&#xff0c;不考虑创…

安卓9.0系统修改定制化____第三方美化 bug修复 移植相关 辅助工具 常识篇 八

在修改rom中。有时候不可避免的需要对系统进行美化以及一些第三方系统的bug修复。在操作前需要了解系统的一些基本常识。例如同平台移植 跨平台移植以及内核移植 apk反编译等等相关的知识。今天解析的这款工具虽然不是直接面向安卓9.0.但对于了解以上的一些必备常识还是不错的 …

云服务器与物理服务器对比:选择最适合的业务服务器解决方案

更多云服务器知识&#xff0c;尽在hostol.com 在现代 IT 基础设施中&#xff0c;云服务器与物理服务器是两种常见的服务器解决方案。随着云计算技术的迅猛发展&#xff0c;越来越多的企业开始转向云服务器&#xff0c;但也有一些企业仍然坚持使用物理服务器&#xff0c;尤其是…

【redis使用场景——缓存——双写一致性】

redis使用场景——缓存——双写一致性 双写一致性问题的本质与场景典型不一致场景分析​​并发写操作导致的不一致​​​​读写交叉导致的不一致​​​​主从同步延迟导致的不一致​​ 解决延迟双删策略&#xff08;推荐&#xff09;优点​​&#xff1a;​​缺点​​&#xff…

【ArcGIS】在线影像底图调用

【ArcGIS】在线影像底图调用 一、 历史影像的调用二、ArcGIS online底图调用三、结语 一、 历史影像的调用 ESRI官方推出了World Imagery Wayback是一个提供全球范围内历史影像的在线服务。 官网地址&#xff1a;https://livingatlas.arcgis.com/wayback/ 操作步骤&#xff1…