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
英文地址,請注明出處:http://en.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…