go源碼閱讀——malloc.go

【博文目錄>>>】 【項目地址>>>】


內存分配器

golang內存分配最初是基於tcmalloc的,但是有很大的不同。tcmalloc文章:
參見:http://goog-perftools.sourceforge.net/doc/tcmalloc.html
翻譯:https://blog.csdn.net/DERRANTCM/article/details/105342996

主分配器在大量頁(runs of pages)中工作。將較小的分配大小(最大爲32 kB,包括32 kB)舍入爲大約70個大小類別之一,每個類別都有其自己的大小完全相同的空閒對象集。任何空閒的內存頁都可以拆分爲一個大小類別的對象集,然後使用空閒位圖(free bitmap)進行管理。

分配器的數據結構爲:

fixalloc:用於固定大小的堆外對象的空閒列表分配器,用於管理分配器使用的存儲。
mheap:malloc堆,以頁(8192字節)粒度進行管理。
mspan:由mheap管理的一系列使用中的頁面。
mcentral:收集給定大小類的所有跨度。
mcache:具有可用空間的mspans的每P個緩存。
mstats:分配統計信息。

分配一個小對象沿用了高速緩存的層次結構:

  • 1、將大小四捨五入爲一個較小的類別,然後在此P的mcache中查看相應的mspan。掃描mspan的空閒位圖以找到空閒位置(slot)。如果有空閒位置,分配它。這都可以在不獲取鎖的情況下完成。
  • 2、如果mspan沒有可用位置,則從mcentral的具有可用空間的所需size類的mspan列表中獲取一個新的mspan。獲得整個跨度(span)會攤銷鎖定mcentral的成本。
  • 3、如果mcentral的mspan列表爲空,從mheap獲取一系列頁以用於mspan。
  • 4、如果mheap爲空或沒有足夠大的頁,則從操作系統中分配一組新的頁(至少1MB)。分配大量頁面將分攤與操作系統進行對話的成本。

清除mspan並釋放對象沿用了類似的層次結構:

  • 1、如果響應分配而清除了mspan,則將mspan返還到mcache以滿足分配。
  • 2、否則,如果mspan仍有已分配的對象,則將其放在mspan的size類別的mcentral空閒列表上。
  • 3、否則,如果mspan中的所有對象都是空閒的,則mspan的頁面將返回到mheap,並且mspan現在已失效。

分配和釋放大對象直接使用mheap,而繞過mcache和mcentral。如果mspan.needzero爲false,則mspan中的可用對象位置已被清零。否則,如果needzero爲true,則在分配對象時將其清零。通過這種方式延遲歸零有很多好處:

  • 1、堆棧幀分配可以完全避免置零。
  • 2、它具有更好的時間局部性,因爲該程序可能即將寫入內存。
  • 3、我們不會將永遠不會被重用的頁面歸零。

虛擬內存佈局

堆由一組arena組成,這些arena在64位上爲64MB,在32位(heapArenaBytes)上爲4MB。每個arena的起始地址也與arena大小對齊。
每個arena都有一個關聯的heapArena對象,該對象存儲該arena的元數據:arena中所有字(word)的堆位圖和arena中所有頁的跨度(span)圖。它們本身是堆外分配的。
由於arena是對齊的,因此可以將地址空間視爲一系列arena幀(frame)。arena映射(mheap_.arenas)從arena幀號映射到*heapArena,對於不由Go堆支持的部分地址空間,映射爲nil。arena映射的結構爲兩層數組,由“L1”arena映射和許多“ L2”arena映射組成;但是,由於arena很大,因此在許多體系結構上,arena映射都由一個大型L2映射組成。
arena地圖覆蓋了整個可用的地址空間,從而允許Go堆使用地址空間的任何部分。分配器嘗試使arena保持連續,以便大跨度(以及大對象)可以跨越arena。

OS內存管理抽象層

在任何給定時間,運行時管理的地址空間區域可能處於四種狀態之一:

  • 1)無(None)——未保留和未映射,這是任何區域的默認狀態。
  • 2)保留(Reserved)——運行時擁有,但是訪問它會導致故障。不計入進程的內存佔用。
  • 3)已準備(Prepared)——保留,意在不由物理內存支持(儘管OS可能會延遲實現)。可以有效過渡到就緒。在這樣的區域中訪問內存是不確定的(可能會出錯,可能會返回意外的零等)。
  • 4)就緒(Ready)——可以安全地訪問。

這組狀態對於支持所有當前受支持的平臺而言絕對不是必需的。只需一個“無”,“保留”和“就緒”就可以解決問題。但是,“已準備”狀態爲我們提供了用於性能目的的靈活性。例如,在POSIX-y操作系統上,“保留”通常是設置了PROT_NONE的私有匿名mmap’d區域,要轉換到“就緒”狀態,需要設置PROT_READ | PROT_WRITE。但是,Prepared的規格不足使我們僅使用MADV_FREE從Ready過渡到Prepared。因此,在“準備好”狀態下,我們可以提早設置一次權限位,我們可以有效地告訴操作系統,當我們嚴格不需要它們時,可以自由地將頁面從我們手中奪走。

對於每個操作系統,都有一組通用的幫助程序,這些幫助程序在這些狀態之間轉換內存區域。幫助程序如下:

sysAlloc

sysAlloc將OS選擇的內存區域從“無”轉換爲“就緒”。更具體地說,它從操作系統中獲取大量的零位內存,通常大約爲一百千字節或兆字節。該內存始終可以立即使用。

sysFree

sysFree將內存區域從任何狀態轉換爲“無(Ready)”。因此,它無條件返回內存。如果在分配過程中檢測到內存不足錯誤,或用於劃分出地址空間的對齊部分,則使用此方法。僅當sysReserve始終返回與堆分配器的對齊限制對齊的內存區域時,如果sysFree是無操作的,這是可以的。

sysReserve

sysReserve將內存區域從“無(None)”轉換爲“保留(Reserved)”。它以這樣一種方式保留地址空間,即在訪問時(通過權限或未提交內存)會導致致命錯誤。因此,這種保留永遠不會受到物理內存的支持。如果傳遞給它的指針爲非nil,則調用者希望在那裏保留,但是sysReserve仍然可以選擇另一個位置(如果該位置不可用)。

注意:sysReserve返回OS對齊的內存,但是堆分配器可能使用更大的對齊方式,因此調用者必須小心地重新對齊sysReserve獲得的內存。

sysMap

sysMap將內存區域從“保留(Reserved)”狀態轉換爲“已準備(Prepared)”狀態。它確保可以將存儲區域有效地轉換爲“就緒(Ready)”。

sysUsed

sysUsed將內存區域從“已準備(Prepared)”過渡到“就緒(Ready)”。它通知操作系統需要內存區域,並確保可以安全地訪問該區域。在沒有明確的提交步驟和嚴格的過量提交限制的系統上,這通常是不操作的,例如,在Windows上至關重要。

sysUnused

sysUnused將內存區域從“就緒(Ready)”轉換爲“已準備(Prepared)”。它通知操作系統,不再需要支持該內存區域的物理頁,並且可以將其重新用於其他目的。 sysUnused內存區域的內容被認爲是沒用的,在調用sysUsed之前,不得再次訪問該區域。

sysFault

sysFault將內存區域從“就緒(Ready)”或“已準備(Prepared)”轉換爲“保留(Reserved)”。它標記了一個區域,以便在訪問時總是會發生故障。僅用於調試運行時。

狀態轉換圖

在這裏插入圖片描述

hint的選擇

64位機器

在64位計算機上,我們選擇以下hit因爲:

  • 1、從地址空間的中間開始,可以輕鬆擴展到連續範圍,而無需運行其他映射。
  • 2、這使Go堆地址在調試時更容易識別。
  • 3、gccgo中的堆棧掃描仍然很保守,因此將地址與其他數據區分開很重要。

從0x00c0開始意味着有效的內存地址將從0x00c0、0x00c1 … n 小端開始,即c0 00,c1 00,…這些都不是有效的UTF-8序列,否則它們是儘可能遠離ff(可能是一個公共字節)。如果失敗,我們嘗試其他0xXXc0地址。較早的嘗試使用0x11f8導致線程分配期間OS X上的內存不足錯誤。 0x00c0導致與AddressSanitizer發生衝突,後者保留了最多0x0100的所有內存。這些選擇減少了保守的垃圾收集器不收集內存的可能性,因爲某些非指針內存塊具有與內存地址匹配的位模式。

但是,在arm64上,我們忽略了上面的所有建議,並在0x40 << 32處分配,因爲當使用具有3級轉換緩衝區的4k頁面時,
用戶地址空間在darwin/arm64上被限制爲39位,地址空間更小。

在AIX上,對於64位,mmaps從0x0A00000000000000開始。

32位機器

在32位計算機上,我們更加關注保持可用堆是連續的。
因此:

  • 1、我們爲所有的heapArena保留空間,這樣它們就不會與heap交錯。 它們約爲258MB,因此還算不錯。(如果出現問題,我們可以在前面預留較小的空間。)
  • 2、我們建議堆從二進制文件的末尾開始,因此我們有最大的機會保持其連續性。
  • 3、我們嘗試放出一個相當大的初始堆保留。

一些常量

  • var zerobase uintptr:所有0字節分配的基地址

源碼

// Memory allocator.
//
// This was originally based on tcmalloc, but has diverged quite a bit.
// http://goog-perftools.sourceforge.net/doc/tcmalloc.html

// The main allocator works in runs of pages.
// Small allocation sizes (up to and including 32 kB) are
// rounded to one of about 70 size classes, each of which
// has its own free set of objects of exactly that size.
// Any free page of memory can be split into a set of objects
// of one size class, which are then managed using a free bitmap.
//
// The allocator's data structures are:
//
//    fixalloc: a free-list allocator for fixed-size off-heap objects,
//        used to manage storage used by the allocator.
//    mheap: the malloc heap, managed at page (8192-byte) granularity.
//    mspan: a run of in-use pages managed by the mheap.
//    mcentral: collects all spans of a given size class.
//    mcache: a per-P cache of mspans with free space.
//    mstats: allocation statistics.
//
// Allocating a small object proceeds up a hierarchy of caches:
//
//    1. Round the size up to one of the small size classes
//       and look in the corresponding mspan in this P's mcache.
//       Scan the mspan's free bitmap to find a free slot.
//       If there is a free slot, allocate it.
//       This can all be done without acquiring a lock.
//
//    2. If the mspan has no free slots, obtain a new mspan
//       from the mcentral's list of mspans of the required size
//       class that have free space.
//       Obtaining a whole span amortizes the cost of locking
//       the mcentral.
//
//    3. If the mcentral's mspan list is empty, obtain a run
//       of pages from the mheap to use for the mspan.
//
//    4. If the mheap is empty or has no page runs large enough,
//       allocate a new group of pages (at least 1MB) from the
//       operating system. Allocating a large run of pages
//       amortizes the cost of talking to the operating system.
//
// Sweeping an mspan and freeing objects on it proceeds up a similar
// hierarchy:
//
//    1. If the mspan is being swept in response to allocation, it
//       is returned to the mcache to satisfy the allocation.
//
//    2. Otherwise, if the mspan still has allocated objects in it,
//       it is placed on the mcentral free list for the mspan's size
//       class.
//
//    3. Otherwise, if all objects in the mspan are free, the mspan's
//       pages are returned to the mheap and the mspan is now dead.
//
// Allocating and freeing a large object uses the mheap
// directly, bypassing the mcache and mcentral.
//
// If mspan.needzero is false, then free object slots in the mspan are
// already zeroed. Otherwise if needzero is true, objects are zeroed as
// they are allocated. There are various benefits to delaying zeroing
// this way:
//
//    1. Stack frame allocation can avoid zeroing altogether.
//
//    2. It exhibits better temporal locality, since the program is
//       probably about to write to the memory.
//
//    3. We don't zero pages that never get reused.

// Virtual memory layout
//
// The heap consists of a set of arenas, which are 64MB on 64-bit and
// 4MB on 32-bit (heapArenaBytes). Each arena's start address is also
// aligned to the arena size.
//
// Each arena has an associated heapArena object that stores the
// metadata for that arena: the heap bitmap for all words in the arena
// and the span map for all pages in the arena. heapArena objects are
// themselves allocated off-heap.
//
// Since arenas are aligned, the address space can be viewed as a
// series of arena frames. The arena map (mheap_.arenas) maps from
// arena frame number to *heapArena, or nil for parts of the address
// space not backed by the Go heap. The arena map is structured as a
// two-level array consisting of a "L1" arena map and many "L2" arena
// maps; however, since arenas are large, on many architectures, the
// arena map consists of a single, large L2 map.
//
// The arena map covers the entire possible address space, allowing
// the Go heap to use any part of the address space. The allocator
// attempts to keep arenas contiguous so that large spans (and hence
// large objects) can cross arenas.
/**
 * 內存分配器
 *
 * 這最初是基於tcmalloc的,但是有很大的不同。
 * 參見:http://goog-perftools.sourceforge.net/doc/tcmalloc.html
 * 翻譯:https://blog.csdn.net/DERRANTCM/article/details/105342996
 *
 * 主分配器在大量頁(runs of pages)中工作。
 * 將較小的分配大小(最大爲32 kB,包括32 kB)舍入爲大約70個大小類別之一,每個類別都有其自己的大小完全相同的空閒對象集。
 * 任何空閒的內存頁都可以拆分爲一個大小類別的對象集,然後使用空閒位圖(free bitmap)進行管理。
 *
 * 分配器的數據結構爲:
 *
 * fixalloc:用於固定大小的堆外對象的空閒列表分配器,用於管理分配器使用的存儲。
 * mheap:malloc堆,以頁(8192字節)粒度進行管理。
 * mspan:由mheap管理的一系列使用中的頁面。
 * mcentral:收集給定大小類的所有跨度。
 * mcache:具有可用空間的mspans的每P個緩存。
 * mstats:分配統計信息。
 *
 * 分配一個小對象沿用了高速緩存的層次結構:
 *
 * 1.將大小四捨五入爲一個較小的類別,然後在此P的mcache中查看相應的mspan。
 * 掃描mspan的空閒位圖以找到空閒位置(slot)。如果有空閒位置,分配它。這都可以在不獲取鎖的情況下完成。
 *
 * 2.如果mspan沒有可用位置,則從mcentral的具有可用空間的所需size類的mspan列表中獲取一個新的mspan。
 * 獲得整個跨度(span)會攤銷鎖定mcentral的成本。
 *
 * 3.如果mcentral的mspan列表爲空,從mheap獲取一系列頁以用於mspan。
 *
 * 4.如果mheap爲空或沒有足夠大的頁,則從操作系統中分配一組新的頁(至少1MB)。
 * 分配大量頁面將分攤與操作系統進行對話的成本。
 *
 * 清除mspan並釋放對象沿用了類似的層次結構:
 *
 * 1.如果響應分配而清除了mspan,則將mspan返還到mcache以滿足分配。
 *
 * 2.否則,如果mspan仍有已分配的對象,則將其放在mspan的size類別的mcentral空閒列表上。
 *
 * 3.否則,如果mspan中的所有對象都是空閒的,則mspan的頁面將返回到mheap,並且mspan現在已失效。
 *
 * 分配和釋放大對象直接使用mheap,而繞過mcache和mcentral。
 *
 * 如果mspan.needzero爲false,則mspan中的可用對象位置已被清零。否則,如果needzero爲true,
 * 則在分配對象時將其清零。通過這種方式延遲歸零有很多好處:
 *
 * 1.堆棧幀分配可以完全避免置零。
 *
 * 2.它具有更好的時間局部性,因爲該程序可能即將寫入內存。
 *
 * 3.我們不會將永遠不會被重用的頁面歸零。
 *
 * 虛擬內存佈局
 *
 * 堆由一組arena組成,這些arena在64位上爲64MB,在32位(heapArenaBytes)上爲4MB。
 * 每個arena的起始地址也與arena大小對齊。
 *
 * 每個arena都有一個關聯的heapArena對象,該對象存儲該arena的元數據:arena中所有字(word)的堆位圖
 * 和arena中所有頁的跨度(span)圖。它們本身是堆外分配的。
 *
 * 由於arena是對齊的,因此可以將地址空間視爲一系列arena幀(frame)。arena映射(mheap_.arenas)
 * 從arena幀號映射到*heapArena,對於不由Go堆支持的部分地址空間,映射爲nil。arena映射的結構爲兩層數組,
 * 由“L1”arena映射和許多“ L2”arena映射組成;但是,由於arena很大,因此在許多體系結構上,
 * arena映射都由一個大型L2映射組成。
 *
 * arena地圖覆蓋了整個可用的地址空間,從而允許Go堆使用地址空間的任何部分。分配器嘗試使arena保持連續,
 * 以便大跨度(以及大對象)可以跨越arena。
 **/

package runtime

import (
    "runtime/internal/atomic"
    "runtime/internal/math"
    "runtime/internal/sys"
    "unsafe"
)

const (
    debugMalloc = false

    maxTinySize   = _TinySize
    tinySizeClass = _TinySizeClass
    maxSmallSize  = _MaxSmallSize

    pageShift = _PageShift
    pageSize  = _PageSize
    pageMask  = _PageMask
    // By construction, single page spans of the smallest object class
    // have the most objects per span.
    // 通過構造,對象類別最小的單個頁面跨度在每個跨度中具有最多的對象。
    // 每個跨度的最大多對象數
    maxObjsPerSpan = pageSize / 8

    concurrentSweep = _ConcurrentSweep

    _PageSize = 1 << _PageShift
    _PageMask = _PageSize - 1

    // _64bit = 1 on 64-bit systems, 0 on 32-bit systems
    // _64bit = 1在64位系統上,0在32位系統上
    _64bit = 1 << (^uintptr(0) >> 63) / 2

    // Tiny allocator parameters, see "Tiny allocator" comment in malloc.go.
    // Tiny分配器參數,請參閱malloc.go中的“Tiny分配器”註釋。在mallocgc方法中
    _TinySize      = 16
    _TinySizeClass = int8(2)
    
    // FixAlloc的塊大小
    _FixAllocChunk = 16 << 10 // Chunk size for FixAlloc

    // Per-P, per order stack segment cache size.
    // 每個P,每個order堆棧段的緩存大小。
    _StackCacheSize = 32 * 1024

    // Number of orders that get caching. Order 0 is FixedStack
    // and each successive order is twice as large.
    // We want to cache 2KB, 4KB, 8KB, and 16KB stacks. Larger stacks
    // will be allocated directly.
    // Since FixedStack is different on different systems, we
    // must vary NumStackOrders to keep the same maximum cached size.
    // 獲得緩存的order數。Order 0是FixedStack,每個連續的order是其兩倍。
    // 我們要緩存2KB,4KB,8KB和16KB堆棧。較大的堆棧將直接分配。
    // 由於FixedStack在不同的系統上是不同的,因此我們必須改變NumStackOrders以保持相同的最大緩存大小。
    // 下面是不同的操作系統對應的FixedStack和NumStackOrders的對應表
    //   OS               | FixedStack | NumStackOrders
    //   -----------------+------------+---------------
    //   linux/darwin/bsd | 2KB        | 4
    //   windows/32       | 4KB        | 3
    //   windows/64       | 8KB        | 2
    //   plan9            | 4KB        | 3
    _NumStackOrders = 4 - sys.PtrSize/4*sys.GoosWindows - 1*sys.GoosPlan9

    // heapAddrBits is the number of bits in a heap address. On
    // amd64, addresses are sign-extended beyond heapAddrBits. On
    // other arches, they are zero-extended.
    //
    // On most 64-bit platforms, we limit this to 48 bits based on a
    // combination of hardware and OS limitations.
    //
    // amd64 hardware limits addresses to 48 bits, sign-extended
    // to 64 bits. Addresses where the top 16 bits are not either
    // all 0 or all 1 are "non-canonical" and invalid. Because of
    // these "negative" addresses, we offset addresses by 1<<47
    // (arenaBaseOffset) on amd64 before computing indexes into
    // the heap arenas index. In 2017, amd64 hardware added
    // support for 57 bit addresses; however, currently only Linux
    // supports this extension and the kernel will never choose an
    // address above 1<<47 unless mmap is called with a hint
    // address above 1<<47 (which we never do).
    //
    // arm64 hardware (as of ARMv8) limits user addresses to 48
    // bits, in the range [0, 1<<48).
    //
    // ppc64, mips64, and s390x support arbitrary 64 bit addresses
    // in hardware. On Linux, Go leans on stricter OS limits. Based
    // on Linux's processor.h, the user address space is limited as
    // follows on 64-bit architectures:
    // heapAddrBits是堆地址中的位數。在amd64上,地址被符號擴展到heapAddrBits之外。在其他架構上,它們是零擴展的。
    //
    // 在大多數64位平臺上,基於硬件和操作系統限制的組合,我們將其限制爲48位。
    //
    // amd64硬件將地址限制爲48位,符號擴展爲64位。前16位不全爲0或全爲1的地址是“非規範的”且無效。
    // 由於存在這些“負”地址,因此在計算進入堆競技場索引的索引之前,在amd64上將地址偏移1 << 47(arenaBaseOffset)。
    // 2017年,amd64硬件增加了對57位地址的支持;但是,當前只有Linux支持此擴展,內核將永遠不會選擇大於1 << 47的地址,
    // 除非調用mmap的提示地址大於1 << 47(我們從未這樣做)。
    //
    // arm64硬件(自ARMv8起)將用戶地址限制爲48位,範圍爲[0,1 << 48)。
    //
    // ppc64,mips64和s390x在硬件中支持任意64位地址。在Linux上,Go依靠更嚴格的OS限制。
    // 基於Linux的processor.h,在64位體系結構上,用戶地址空間受到如下限制:
    //
    // Architecture  Name              Maximum Value (exclusive)
    // ---------------------------------------------------------------------
    // amd64         TASK_SIZE_MAX     0x007ffffffff000 (47 bit addresses)
    // arm64         TASK_SIZE_64      0x01000000000000 (48 bit addresses)
    // ppc64{,le}    TASK_SIZE_USER64  0x00400000000000 (46 bit addresses)
    // mips64{,le}   TASK_SIZE64       0x00010000000000 (40 bit addresses)
    // s390x         TASK_SIZE         1<<64 (64 bit addresses)
    //
    // These limits may increase over time, but are currently at
    // most 48 bits except on s390x. On all architectures, Linux
    // starts placing mmap'd regions at addresses that are
    // significantly below 48 bits, so even if it's possible to
    // exceed Go's 48 bit limit, it's extremely unlikely in
    // practice.
    //
    // On 32-bit platforms, we accept the full 32-bit address
    // space because doing so is cheap.
    // mips32 only has access to the low 2GB of virtual memory, so
    // we further limit it to 31 bits.
    //
    // On darwin/arm64, although 64-bit pointers are presumably
    // available, pointers are truncated to 33 bits. Furthermore,
    // only the top 4 GiB of the address space are actually available
    // to the application, but we allow the whole 33 bits anyway for
    // simplicity.
    // TODO(mknyszek): Consider limiting it to 32 bits and using
    // arenaBaseOffset to offset into the top 4 GiB.
    //
    // WebAssembly currently has a limit of 4GB linear memory.
    // 這些限制可能會隨時間增加,但目前最多爲48位,但s390x除外。在所有體系結構上,
    // Linux都開始將mmap'd區域放置在明顯低於48位的地址上,因此,即使有可能超過Go的48位限制,在實踐中也極不可能。
    //
    // 在32位平臺上,我們接受完整的32位地址空間,因爲這樣做很便宜。 mips32僅可以訪問2GB的低虛擬內存,
    // 因此我們進一步將其限制爲31位。
    //
    // 在darwin / arm64上,儘管大概可以使用64位指針,但指針會被截斷爲33位。此外,
    // 只有地址空間的前4個GiB實際上可供應用程序使用,但是爲了簡單起見,我們還是允許全部33位。
    // TODO(mknyszek):考慮將其限制爲32位,並使用arenaBaseOffset偏移到前4個GiB中。
    //
    // WebAssembly當前限制爲4GB線性內存。
    // heapAddrBits:堆空間地址位數,間接表示了他可以支持的最大的內存空間
    heapAddrBits = (_64bit*(1-sys.GoarchWasm)*(1-sys.GoosDarwin*sys.GoarchArm64))*48 + (1-_64bit+sys.GoarchWasm)*(32-(sys.GoarchMips+sys.GoarchMipsle)) + 33*sys.GoosDarwin*sys.GoarchArm64

    // maxAlloc is the maximum size of an allocation. On 64-bit,
    // it's theoretically possible to allocate 1<<heapAddrBits bytes. On
    // 32-bit, however, this is one less than 1<<32 because the
    // number of bytes in the address space doesn't actually fit
    // in a uintptr.
    // maxAlloc是分配的最大大小。在64位上,理論上可以分配1 << heapAddrBits字節。
    // 但是,在32位上,這比1<<32小1,因爲地址空間中的字節數實際上不適合uintptr。
    maxAlloc = (1 << heapAddrBits) - (1-_64bit)*1

    // The number of bits in a heap address, the size of heap
    // arenas, and the L1 and L2 arena map sizes are related by
    //
    //   (1 << addr bits) = arena size * L1 entries * L2 entries
    //
    // Currently, we balance these as follows:
    // 堆地址中的位數,堆arena的大小以及L1和L2 arena映射的大小與
    //
    // 1 << addr位)=arena大小* L1條目* L2條目
    //
    // 目前,我們將這些平衡如下:
    //
    //       Platform  Addr bits  Arena size  L1 entries   L2 entries
    // --------------  ---------  ----------  ----------  -----------
    //       */64-bit         48        64MB           1    4M (32MB)
    // windows/64-bit         48         4MB          64    1M  (8MB)
    //       */32-bit         32         4MB           1  1024  (4KB)
    //     */mips(le)         31         4MB           1   512  (2KB)

    // heapArenaBytes is the size of a heap arena. The heap
    // consists of mappings of size heapArenaBytes, aligned to
    // heapArenaBytes. The initial heap mapping is one arena.
    //
    // This is currently 64MB on 64-bit non-Windows and 4MB on
    // 32-bit and on Windows. We use smaller arenas on Windows
    // because all committed memory is charged to the process,
    // even if it's not touched. Hence, for processes with small
    // heaps, the mapped arena space needs to be commensurate.
    // This is particularly important with the race detector,
    // since it significantly amplifies the cost of committed
    // memory.
    // heapArenaBytes是堆arenas的大小。堆由大小爲heapArenaBytes的映射組成,
    // 並與heapArenaBytes對齊。最初的堆映射是一個arenas。
    //
    // 當前在64位非Windows上爲64MB,在32位和Windows上爲4MB。我們在Windows上使用較小的arenas,
    // 因爲所有已提交的內存都由進程負責,即使未涉及也是如此。因此,對於具有小堆的進程,映射的arenas空間需要相對應。
    // 這對於競爭檢測器尤其重要,因爲它會大大增加已提交內存的成本。
    heapArenaBytes = 1 << logHeapArenaBytes

    // logHeapArenaBytes is log_2 of heapArenaBytes. For clarity,
    // prefer using heapArenaBytes where possible (we need the
    // constant to compute some other constants).
    // logHeapArenaBytes是heapArenaBytes的log_2。爲了清楚起見,
    // 最好在可能的地方使用heapArenaBytes(我們需要使用常量來計算其他常量)。
    logHeapArenaBytes = (6+20)*(_64bit*(1-sys.GoosWindows)*(1-sys.GoarchWasm)) + (2+20)*(_64bit*sys.GoosWindows) + (2+20)*(1-_64bit) + (2+20)*sys.GoarchWasm

    // heapArenaBitmapBytes is the size of each heap arena's bitmap.
    // heapArenaBitmapBytes是每個堆arena的位圖大小。
    heapArenaBitmapBytes = heapArenaBytes / (sys.PtrSize * 8 / 2)

    // 每個arena所的頁數
    pagesPerArena = heapArenaBytes / pageSize

    // arenaL1Bits is the number of bits of the arena number
    // covered by the first level arena map.
    //
    // This number should be small, since the first level arena
    // map requires PtrSize*(1<<arenaL1Bits) of space in the
    // binary's BSS. It can be zero, in which case the first level
    // index is effectively unused. There is a performance benefit
    // to this, since the generated code can be more efficient,
    // but comes at the cost of having a large L2 mapping.
    //
    // We use the L1 map on 64-bit Windows because the arena size
    // is small, but the address space is still 48 bits, and
    // there's a high cost to having a large L2.
    // arenaL1Bits是第一級arena映射覆蓋的arena編號的位數。
    //
    // 這個數字應該很小,因爲第一級arena映射在二進制文件的BSS中需要PtrSize*(1<<arenaL1Bits)空間。
    // 它可以爲零,在這種情況下,第一級索引實際上未被使用。這會帶來性能上的好處,
    // 因爲生成的代碼可以更高效,但是以擁有較大的L2映射爲代價。
    //
    // 我們在64位Windows上使用L1映射,因爲arena大小很小,但是地址空間仍然是48位,並且擁有大型L2的成本很高。
    arenaL1Bits = 6 * (_64bit * sys.GoosWindows)

    // arenaL2Bits is the number of bits of the arena number
    // covered by the second level arena index.
    //
    // The size of each arena map allocation is proportional to
    // 1<<arenaL2Bits, so it's important that this not be too
    // large. 48 bits leads to 32MB arena index allocations, which
    // is about the practical threshold.
    // arenaL2Bits是第二級arena索引覆蓋的arena編號的位數。
    //
    // 每個arena映射分配的大小與1<<arenaL2Bits成正比,因此,不要太大也很重要。
    // 48位導致32MB arena索引分配,這大約是實際的閾值。
    arenaL2Bits = heapAddrBits - logHeapArenaBytes - arenaL1Bits

    // arenaL1Shift is the number of bits to shift an arena frame
    // number by to compute an index into the first level arena map.
    // arenaL1Shift是將arena幀號移位以計算進入第一級arena映射的索引的位數。
    arenaL1Shift = arenaL2Bits

    // arenaBits is the total bits in a combined arena map index.
    // This is split between the index into the L1 arena map and
    // the L2 arena map.
    // arenaBits是組合的arena映射索引中的總位。這在進入L1 arena映射和L2 arena映射的索引之間進行劃分。
    arenaBits = arenaL1Bits + arenaL2Bits

    // arenaBaseOffset is the pointer value that corresponds to
    // index 0 in the heap arena map.
    //
    // On amd64, the address space is 48 bits, sign extended to 64
    // bits. This offset lets us handle "negative" addresses (or
    // high addresses if viewed as unsigned).
    //
    // On aix/ppc64, this offset allows to keep the heapAddrBits to
    // 48. Otherwize, it would be 60 in order to handle mmap addresses
    // (in range 0x0a00000000000000 - 0x0afffffffffffff). But in this
    // case, the memory reserved in (s *pageAlloc).init for chunks
    // is causing important slowdowns.
    //
    // On other platforms, the user address space is contiguous
    // and starts at 0, so no offset is necessary.
    // arenaBaseOffset是與堆arena映射中的索引0對應的指針值。
    //
    // 在amd64上,地址空間爲48位,符號擴展爲64位。此偏移量使我們可以處理“負”地址(如果視爲無符號,則爲高地址)。
    //
    // 在aix/ppc64上,此偏移量允許將heapAddrBits保持爲48。否則,爲了處理mmap地址
    //(範圍爲0x0a00000000000000-0x0afffffffffffffff),它將爲60。但是在這種情況下,
    // (s*pageAlloc).init中爲塊保留的內存會導致嚴重的速度下降。
    //
    // 在其他平臺上,用戶地址空間是連續的,並且從0開始,因此不需要偏移量。
    arenaBaseOffset = sys.GoarchAmd64*(1<<47) + (^0x0a00000000000000+1)&uintptrMask*sys.GoosAix

    // Max number of threads to run garbage collection.
    // 2, 3, and 4 are all plausible maximums depending
    // on the hardware details of the machine. The garbage
    // collector scales well to 32 cpus.
    // 運行垃圾回收的最大線程數。 2、3和4都是合理的最大值,具體取決於機器的硬件細節。 垃圾收集器可以很好地擴展到32 cpus。
    _MaxGcproc = 32

    // minLegalPointer is the smallest possible legal pointer.
    // This is the smallest possible architectural page size,
    // since we assume that the first page is never mapped.
    //
    // This should agree with minZeroPage in the compiler.
    //
    // minLegalPointer是最小的合法指針。 這是可能的最小體系架構頁大小,因爲我們假設第一頁從未映射過。
    //這應該與編譯器中的minZeroPage一致。
    minLegalPointer uintptr = 4096
)

// physPageSize is the size in bytes of the OS's physical pages.
// Mapping and unmapping operations must be done at multiples of
// physPageSize.
//
// This must be set by the OS init code (typically in osinit) before
// mallocinit.
//
// physPageSize是操作系統物理頁面的大小(以字節爲單位)。
// 映射和取消映射操作必須以physPageSize的倍數完成。
//
// 必須在mallocinit之前通過OS初始化代碼(通常在osinit中)進行設置。
var physPageSize uintptr

// physHugePageSize is the size in bytes of the OS's default physical huge
// page size whose allocation is opaque to the application. It is assumed
// and verified to be a power of two.
//
// If set, this must be set by the OS init code (typically in osinit) before
// mallocinit. However, setting it at all is optional, and leaving the default
// value is always safe (though potentially less efficient).
//
// Since physHugePageSize is always assumed to be a power of two,
// physHugePageShift is defined as physHugePageSize == 1 << physHugePageShift.
// The purpose of physHugePageShift is to avoid doing divisions in
// performance critical functions.
//
// physHugePageSize是操作系統默認物理大頁面大小的大小(以字節爲單位),
// 該大小對於應用程序是不透明的。 假定並驗證爲2的冪。
//
// 如果已設置,則必須在mallocinit之前通過OS初始化代碼(通常在osinit中)進行設置。
// 但是,完全設置它是可選的,並且保留默認值始終是安全的(儘管可能會降低效率)。
//
// 由於physHugePageSize始終假定爲2的冪,因此physHugePageShift定義爲physHugePageSize == 1 << physHugePageShift。
// physHugePageShift的目的是避免對性能至關重要的功能進行劃分。
var (
    physHugePageSize  uintptr
    physHugePageShift uint
)

// OS memory management abstraction layer
//
// Regions of the address space managed by the runtime may be in one of four
// states at any given time:
// 1) None - Unreserved and unmapped, the default state of any region.
// 2) Reserved - Owned by the runtime, but accessing it would cause a fault.
//               Does not count against the process' memory footprint.
// 3) Prepared - Reserved, intended not to be backed by physical memory (though
//               an OS may implement this lazily). Can transition efficiently to
//               Ready. Accessing memory in such a region is undefined (may
//               fault, may give back unexpected zeroes, etc.).
// 4) Ready - may be accessed safely.
//
// This set of states is more than is strictly necessary to support all the
// currently supported platforms. One could get by with just None, Reserved, and
// Ready. However, the Prepared state gives us flexibility for performance
// purposes. For example, on POSIX-y operating systems, Reserved is usually a
// private anonymous mmap'd region with PROT_NONE set, and to transition
// to Ready would require setting PROT_READ|PROT_WRITE. However the
// underspecification of Prepared lets us use just MADV_FREE to transition from
// Ready to Prepared. Thus with the Prepared state we can set the permission
// bits just once early on, we can efficiently tell the OS that it's free to
// take pages away from us when we don't strictly need them.
//
// For each OS there is a common set of helpers defined that transition
// memory regions between these states. The helpers are as follows:
//
// sysAlloc transitions an OS-chosen region of memory from None to Ready.
// More specifically, it obtains a large chunk of zeroed memory from the
// operating system, typically on the order of a hundred kilobytes
// or a megabyte. This memory is always immediately available for use.
//
// sysFree transitions a memory region from any state to None. Therefore, it
// returns memory unconditionally. It is used if an out-of-memory error has been
// detected midway through an allocation or to carve out an aligned section of
// the address space. It is okay if sysFree is a no-op only if sysReserve always
// returns a memory region aligned to the heap allocator's alignment
// restrictions.
//
// sysReserve transitions a memory region from None to Reserved. It reserves
// address space in such a way that it would cause a fatal fault upon access
// (either via permissions or not committing the memory). Such a reservation is
// thus never backed by physical memory.
// If the pointer passed to it is non-nil, the caller wants the
// reservation there, but sysReserve can still choose another
// location if that one is unavailable.
// NOTE: sysReserve returns OS-aligned memory, but the heap allocator
// may use larger alignment, so the caller must be careful to realign the
// memory obtained by sysReserve.
//
// sysMap transitions a memory region from Reserved to Prepared. It ensures the
// memory region can be efficiently transitioned to Ready.
//
// sysUsed transitions a memory region from Prepared to Ready. It notifies the
// operating system that the memory region is needed and ensures that the region
// may be safely accessed. This is typically a no-op on systems that don't have
// an explicit commit step and hard over-commit limits, but is critical on
// Windows, for example.
//
// sysUnused transitions a memory region from Ready to Prepared. It notifies the
// operating system that the physical pages backing this memory region are no
// longer needed and can be reused for other purposes. The contents of a
// sysUnused memory region are considered forfeit and the region must not be
// accessed again until sysUsed is called.
//
// sysFault transitions a memory region from Ready or Prepared to Reserved. It
// marks a region such that it will always fault if accessed. Used only for
// debugging the runtime.
/**
 * OS內存管理抽象層
 *
 * 在任何給定時間,運行時管理的地址空間區域可能處於四種狀態之一:
 * - 1)無(None)——未保留和未映射,這是任何區域的默認狀態。
 * - 2)保留(Reserved)——運行時擁有,但是訪問它會導致故障。不計入進程的內存佔用。
 * - 3)已準備(Prepared)——保留,意在不由物理內存支持(儘管OS可能會延遲實現)。
 *      可以有效過渡到就緒。在這樣的區域中訪問內存是不確定的(可能會出錯,可能會返回意外的零等)。
 * - 4)就緒(Ready)——可以安全地訪問。
 *
 * 這組狀態對於支持所有當前受支持的平臺而言絕對不是必需的。只需一個“無”,“保留”和“就緒”就可以解決問題。
 * 但是,“已準備”狀態爲我們提供了用於性能目的的靈活性。例如,在POSIX-y操作系統上,“保留”通常是設置了PROT_NONE的私有匿名mmap'd區域,
 * 要轉換到“就緒”狀態,需要設置PROT_READ | PROT_WRITE。但是,Prepared的規格不足使我們僅使用MADV_FREE從Ready過渡到Prepared。
 * 因此,在“準備好”狀態下,我們可以提早設置一次權限位,我們可以有效地告訴操作系統,當我們嚴格不需要它們時,可以自由地將頁面從我們手中奪走。
 *
 * 對於每個操作系統,都有一組通用的幫助程序,這些幫助程序在這些狀態之間轉換內存區域。幫助程序如下:
 *
 * sysAlloc
 * sysAlloc將OS選擇的內存區域從“無”轉換爲“就緒”。更具體地說,它從操作系統中獲取大量的零位內存,通常大約爲一百千字節或兆字節。
 * 該內存始終可以立即使用。
 *
 * sysFree
 * sysFree將內存區域從任何狀態轉換爲“無(Ready)”。因此,它無條件返回內存。如果在分配過程中檢測到內存不足錯誤,
 * 或用於劃分出地址空間的對齊部分,則使用此方法。僅當sysReserve始終返回與堆分配器的對齊限制對齊的內存區域時,
 * 如果sysFree是無操作的,這是可以的。
 *
 * sysReserve
 * sysReserve將內存區域從“無(None)”轉換爲“保留(Reserved)”。它以這樣一種方式保留地址空間,
 * 即在訪問時(通過權限或未提交內存)會導致致命錯誤。因此,這種保留永遠不會受到物理內存的支持。如果傳遞給它的指針爲非nil,
 * 則調用者希望在那裏保留,但是sysReserve仍然可以選擇另一個位置(如果該位置不可用)。
 *
 * 注意:sysReserve返回OS對齊的內存,但是堆分配器可能使用更大的對齊方式,因此調用者必須小心地重新對齊sysReserve獲得的內存。
 *
 * sysMap
 * sysMap將內存區域從“保留(Reserved)”狀態轉換爲“已準備(Prepared)”狀態。它確保可以將存儲區域有效地轉換爲“就緒(Ready)”。
 *
 * sysUsed
 * sysUsed將內存區域從“已準備(Prepared)”過渡到“就緒(Ready)”。它通知操作系統需要內存區域,並確保可以安全地訪問該區域。
 * 在沒有明確的提交步驟和嚴格的過量提交限制的系統上,這通常是不操作的,例如,在Windows上至關重要。
 *
 * sysUnused
 * sysUnused將內存區域從“就緒(Ready)”轉換爲“已準備(Prepared)”。它通知操作系統,不再需要支持該內存區域的物理頁,
 * 並且可以將其重新用於其他目的。 sysUnused內存區域的內容被認爲是沒用的,在調用sysUsed之前,不得再次訪問該區域。
 *
 * sysFault
 * sysFault將內存區域從“就緒(Ready)”或“已準備(Prepared)”轉換爲“保留(Reserved)”。它標記了一個區域,
 * 以便在訪問時總是會發生故障。僅用於調試運行時。
 **/
func mallocinit() {
    // 檢查_TinySizeClass與_TinySize對應關係
    if class_to_size[_TinySizeClass] != _TinySize {
        throw("bad TinySizeClass")
    }

    // 確保映射到相同defer大小類別的defer arg大小也映射到相同的malloc大小類別。
    testdefersizes()

    // 判斷heapArenaBitmapBytes是否是2的指數次方
    if heapArenaBitmapBytes&(heapArenaBitmapBytes-1) != 0 {
        // heapBits expects modular arithmetic on bitmap
        // addresses to work.
        // heapBits希望對位圖地址進行模塊化算術運算。
        throw("heapArenaBitmapBytes not a power of 2")
    }

    // Copy class sizes out for statistics table.
    // 將類別大小拷貝到統計表
    for i := range class_to_size {
        memstats.by_size[i].size = uint32(class_to_size[i])
    }

    // Check physPageSize.
    // 檢查physPageSize。
    if physPageSize == 0 {
        // The OS init code failed to fetch the physical page size.
        // 操作系統初始化代碼無法獲取物理頁面大小。
        throw("failed to get system page size")
    }
    // 物理頁大小比最大物理頁還大
    if physPageSize > maxPhysPageSize {
        print("system page size (", physPageSize, ") is larger than maximum page size (", maxPhysPageSize, ")\n")
        throw("bad system page size")
    }
    // 物理頁大小比最小物理頁還小
    if physPageSize < minPhysPageSize {
        print("system page size (", physPageSize, ") is smaller than minimum page size (", minPhysPageSize, ")\n")
        throw("bad system page size")
    }
    // 物理頁必須是2的冪次方
    if physPageSize&(physPageSize-1) != 0 {
        print("system page size (", physPageSize, ") must be a power of 2\n")
        throw("bad system page size")
    }
    // 操作系統默認頁大小,物理頁必須是2的冪次方
    if physHugePageSize&(physHugePageSize-1) != 0 {
        print("system huge page size (", physHugePageSize, ") must be a power of 2\n")
        throw("bad system huge page size")
    }
    // 操作系統默認頁大小大於操作系統最大的頁大小
    if physHugePageSize > maxPhysHugePageSize {
        // physHugePageSize is greater than the maximum supported huge page size.
        // Don't throw here, like in the other cases, since a system configured
        // in this way isn't wrong, we just don't have the code to support them.
        // Instead, silently set the huge page size to zero.
        // physHugePageSize大於所支持的最大大頁面大小。不要像其他情況那樣在這裏throw錯誤,
        // 因爲以這種方式配置的系統沒有錯,所以我們只是沒有支持它們的代碼。而是將巨大的頁面大小靜默設置爲零。
        physHugePageSize = 0
    }
    if physHugePageSize != 0 {
        // Since physHugePageSize is a power of 2, it suffices to increase
        // physHugePageShift until 1<<physHugePageShift == physHugePageSize.
        // 由於physHugePageSize爲2的冪,因此足以將physHugePageShift增大到1<<physHugePageShift == physHugePageSize。
        for 1<<physHugePageShift != physHugePageSize {
            physHugePageShift++
        }
    }

    // Initialize the heap.
    // 初始化堆
    mheap_.init()
    _g_ := getg()
    _g_.m.mcache = allocmcache()

    // Create initial arena growth hints.
    // 創建初始arena增長提示。8表示字節數
    if sys.PtrSize == 8 {
        // On a 64-bit machine, we pick the following hints
        // because:
        //
        // 1. Starting from the middle of the address space
        // makes it easier to grow out a contiguous range
        // without running in to some other mapping.
        //
        // 2. This makes Go heap addresses more easily
        // recognizable when debugging.
        //
        // 3. Stack scanning in gccgo is still conservative,
        // so it's important that addresses be distinguishable
        // from other data.
        //
        // Starting at 0x00c0 means that the valid memory addresses
        // will begin 0x00c0, 0x00c1, ...
        // In little-endian, that's c0 00, c1 00, ... None of those are valid
        // UTF-8 sequences, and they are otherwise as far away from
        // ff (likely a common byte) as possible. If that fails, we try other 0xXXc0
        // addresses. An earlier attempt to use 0x11f8 caused out of memory errors
        // on OS X during thread allocations.  0x00c0 causes conflicts with
        // AddressSanitizer which reserves all memory up to 0x0100.
        // These choices reduce the odds of a conservative garbage collector
        // not collecting memory because some non-pointer block of memory
        // had a bit pattern that matched a memory address.
        //
        // However, on arm64, we ignore all this advice above and slam the
        // allocation at 0x40 << 32 because when using 4k pages with 3-level
        // translation buffers, the user address space is limited to 39 bits
        // On darwin/arm64, the address space is even smaller.
        //
        // On AIX, mmaps starts at 0x0A00000000000000 for 64-bit.
        // processes.
        //
        // 在64位計算機上,我們選擇以下hit因爲:
        //
        // 1.從地址空間的中間開始,可以輕鬆擴展到連續範圍,而無需運行其他映射。
        //
        // 2.這使Go堆地址在調試時更容易識別。
        //
        // 3. gccgo中的堆棧掃描仍然很保守,因此將地址與其他數據區分開很重要。
        //
        // 從0x00c0開始意味着有效的內存地址將從0x00c0、0x00c1 ... n 小端開始,即c0 00,c1 00,...
        // 這些都不是有效的UTF-8序列,否則它們是儘可能遠離ff(可能是一個公共字節)。
        // 如果失敗,我們嘗試其他0xXXc0地址。較早的嘗試使用0x11f8導致線程分配期間OS X上的內存不足錯誤。
        // 0x00c0導致與AddressSanitizer發生衝突,後者保留了最多0x0100的所有內存。
        // 這些選擇減少了保守的垃圾收集器不收集內存的可能性,因爲某些非指針內存塊具有與內存地址匹配的位模式。
        //
        // 但是,在arm64上,我們忽略了上面的所有建議,並在0x40 << 32處分配,因爲當使用具有3級轉換緩衝區的4k頁面時,
        // 用戶地址空間在darwin/arm64上被限制爲39位,地址空間更小。
        //
        // 在AIX上,對於64位,mmaps從0x0A00000000000000開始。
        // 預先進行內存分配,最多分配64次
        for i := 0x7f; i >= 0; i-- { // i=0b01111111
            var p uintptr
            switch {
            case GOARCH == "arm64" && GOOS == "darwin":
                p = uintptr(i)<<40 | uintptrMask&(0x0013<<28)
            case GOARCH == "arm64":
                p = uintptr(i)<<40 | uintptrMask&(0x0040<<32)
            case GOOS == "aix":
                if i == 0 {
                    // We don't use addresses directly after 0x0A00000000000000
                    // to avoid collisions with others mmaps done by non-go programs.
                    // 我們不會在0x0A00000000000000之後直接使用地址,以免與非執行程序造成的其他mmap衝突。
                    continue
                }
                p = uintptr(i)<<40 | uintptrMask&(0xa0<<52)
            case raceenabled: // 此值已爲false
                // The TSAN runtime requires the heap
                // to be in the range [0x00c000000000,
                // 0x00e000000000).
                p = uintptr(i)<<32 | uintptrMask&(0x00c0<<32)
                if p >= uintptrMask&0x00e000000000 {
                    continue
                }
            default:
                p = uintptr(i)<<40 | uintptrMask&(0x00c0<<32)
            }
            // p是所求的每個hint起始地址
            // 採用頭插法對hint塊進行拉鍊,小端地址在前
            // 最終所有的地址都分配在了mheap_.arenaHints上
            hint := (*arenaHint)(mheap_.arenaHintAlloc.alloc())
            hint.addr = p
            hint.next, mheap_.arenaHints = mheap_.arenaHints, hint
        }
    } else {
        // On a 32-bit machine, we're much more concerned
        // about keeping the usable heap contiguous.
        // Hence:
        //
        // 1. We reserve space for all heapArenas up front so
        // they don't get interleaved with the heap. They're
        // ~258MB, so this isn't too bad. (We could reserve a
        // smaller amount of space up front if this is a
        // problem.)
        //
        // 2. We hint the heap to start right above the end of
        // the binary so we have the best chance of keeping it
        // contiguous.
        //
        // 3. We try to stake out a reasonably large initial
        // heap reservation.
        //
        // 在32位計算機上,我們更加關注保持可用堆是連續的。
        // 因此:
        //
        // 1.我們爲所有的heapArena保留空間,這樣它們就不會與heap交錯。 它們約爲258MB,因此還算不錯。
        // (如果出現問題,我們可以在前面預留較小的空間。)
        //
        // 2.我們建議堆從二進制文件的末尾開始,因此我們有最大的機會保持其連續性。
        //
        // 3.我們嘗試放出一個相當大的初始堆保留。
        // 計算arena元數據大小
        const arenaMetaSize = (1 << arenaBits) * unsafe.Sizeof(heapArena{})
        // 保留內存
        meta := uintptr(sysReserve(nil, arenaMetaSize))
        if meta != 0 { // 保留成功,就進行初始化
            mheap_.heapArenaAlloc.init(meta, arenaMetaSize)
        }

        // We want to start the arena low, but if we're linked
        // against C code, it's possible global constructors
        // have called malloc and adjusted the process' brk.
        // Query the brk so we can avoid trying to map the
        // region over it (which will cause the kernel to put
        // the region somewhere else, likely at a high
        // address).
        // 我們想從低arena地址開始,但是如果我們與C代碼鏈接,則可能全局構造函數調用了malloc並調整了進程的brk。
        // 查詢brk,以便我們避免嘗試在其上映射區域(這將導致內核將區域放置在其他地方,可能位於高地址)。
        // brk和sbrk相關文檔
        // https://blog.csdn.net/yusiguyuan/article/details/39496057
        // https://blog.csdn.net/Apollon_krj/article/details/54565768
        procBrk := sbrk0()

        // If we ask for the end of the data segment but the
        // operating system requires a little more space
        // before we can start allocating, it will give out a
        // slightly higher pointer. Except QEMU, which is
        // buggy, as usual: it won't adjust the pointer
        // upward. So adjust it upward a little bit ourselves:
        // 1/4 MB to get away from the running binary image.
        // 如果我們要求結束數據段,但是操作系統在開始分配之前需要更多空間,它將給出稍高的指針。
        // 像往常一樣,除了QEMU之外,它還有很多問題:它不會向上調整指針。 因此,我們自己向上調整一點:
        // 1/4 MB以遠離正在運行的二進制映像。
        p := firstmoduledata.end
        if p < procBrk {
            p = procBrk
        }
        if mheap_.heapArenaAlloc.next <= p && p < mheap_.heapArenaAlloc.end {
            p = mheap_.heapArenaAlloc.end
        }
        // alignUp(n, a) alignUp將n舍入爲a的倍數。 a必須是2的冪。
        p = alignUp(p+(256<<10), heapArenaBytes)
        // Because we're worried about fragmentation on
        // 32-bit, we try to make a large initial reservation.
        // 因爲我們擔心32位上的碎片,所以我們嘗試進行較大的初始保留。
        arenaSizes := []uintptr{
            512 << 20,
            256 << 20,
            128 << 20,
        }
        // 從在到小嚐試分配,首次分配好就結束
        for _, arenaSize := range arenaSizes {
            // sysReserveAligned類似於sysReserve,但是返回的指針字節對齊的。
            // 它可以保留n個或n+align個字節,因此它返回保留的大小。
            a, size := sysReserveAligned(unsafe.Pointer(p), arenaSize, heapArenaBytes)
            if a != nil {
                mheap_.arena.init(uintptr(a), size)
                p = uintptr(a) + size // For hint below
                break
            }
        }
        hint := (*arenaHint)(mheap_.arenaHintAlloc.alloc())
        hint.addr = p
        hint.next, mheap_.arenaHints = mheap_.arenaHints, hint
    }
}

// sysAlloc allocates heap arena space for at least n bytes. The
// returned pointer is always heapArenaBytes-aligned and backed by
// h.arenas metadata. The returned size is always a multiple of
// heapArenaBytes. sysAlloc returns nil on failure.
// There is no corresponding free function.
//
// sysAlloc returns a memory region in the Prepared state. This region must
// be transitioned to Ready before use.
//
// h must be locked.
/**
 * sysAlloc至少爲n個字節分配堆arena空間。返回的指針始終是heapArenaBytes對齊的,
 * 並由h.arenas元數據支持。返回的大小始終是heapArenaBytes的倍數。 sysAlloc失敗時返回nil。
 * 沒有相應的free函數。
 *
 * sysAlloc返回處於Prepared狀態的內存區域。使用前,該區域必須轉換爲“就緒”。
 *
 * h必須被鎖定。
 * @param n 待分配的字節數
 * @return v 地址指針
 * @return size 分配的字節數
 **/
func (h *mheap) sysAlloc(n uintptr) (v unsafe.Pointer, size uintptr) {
    // 進行字節對齊
    n = alignUp(n, heapArenaBytes)

    // First, try the arena pre-reservation.
    // 首先,嘗試arena預定。
    v = h.arena.alloc(n, heapArenaBytes, &memstats.heap_sys)
    if v != nil {
        size = n
        goto mapped
    }

    // Try to grow the heap at a hint address.
    // 嘗試在hint地址處增加堆。
    for h.arenaHints != nil {
        hint := h.arenaHints
        p := hint.addr
        if hint.down {
            p -= n
        }
        if p+n < p {
            // We can't use this, so don't ask.
            // 我們不能使用它,所以不迴應。
            v = nil
        } else if arenaIndex(p+n-1) >= 1<<arenaBits {
            // Outside addressable heap. Can't use.
            // 外部可尋址堆。無法使用。
            v = nil
        } else {
            v = sysReserve(unsafe.Pointer(p), n)
        }
        if p == uintptr(v) {
            // Success. Update the hint.
            // 成功。更新hint。
            if !hint.down {
                p += n
            }
            hint.addr = p
            size = n
            break
        }
        // Failed. Discard this hint and try the next.
        //
        // TODO: This would be cleaner if sysReserve could be
        // told to only return the requested address. In
        // particular, this is already how Windows behaves, so
        // it would simplify things there.
        // 失敗了放棄此hint,然後嘗試下一個。
        //
        // TODO:如果可以告訴sysReserve僅返回所請求的地址,則這樣做會更清潔。
        // 特別是,這已經是Windows的處理方式,因此它將簡化那裏的事情。
        if v != nil {
            sysFree(v, n, nil)
        }
        h.arenaHints = hint.next
        h.arenaHintAlloc.free(unsafe.Pointer(hint))
    }

    if size == 0 {
        if raceenabled { // 此值已爲false
            // The race detector assumes the heap lives in
            // [0x00c000000000, 0x00e000000000), but we
            // just ran out of hints in this region. Give
            // a nice failure.
            throw("too many address space collisions for -race mode")
        }

        // All of the hints failed, so we'll take any
        // (sufficiently aligned) address the kernel will give
        // us.
        // 所有hint均失敗,因此我們將採用內核將提供給我們的任何地址(已充分對齊)。
        v, size = sysReserveAligned(nil, n, heapArenaBytes)
        if v == nil {
            return nil, 0
        }

        // Create new hints for extending this region.
        // 創建用於擴展此區域的新hint。
        hint := (*arenaHint)(h.arenaHintAlloc.alloc())
        hint.addr, hint.down = uintptr(v), true
        hint.next, mheap_.arenaHints = mheap_.arenaHints, hint
        hint = (*arenaHint)(h.arenaHintAlloc.alloc())
        hint.addr = uintptr(v) + size
        hint.next, mheap_.arenaHints = mheap_.arenaHints, hint
    }

    // Check for bad pointers or pointers we can't use.
    // 檢查錯誤的指針或我們不能使用的指針。
    {
        var bad string
        p := uintptr(v)
        if p+size < p {
            bad = "region exceeds uintptr range"
        } else if arenaIndex(p) >= 1<<arenaBits {
            bad = "base outside usable address space"
        } else if arenaIndex(p+size-1) >= 1<<arenaBits {
            bad = "end outside usable address space"
        }
        if bad != "" {
            // This should be impossible on most architectures,
            // but it would be really confusing to debug.
            // 在大多數體系結構上,這應該是不可能的,但是調試起來確實很混亂。
            print("runtime: memory allocated by OS [", hex(p), ", ", hex(p+size), ") not in usable address space: ", bad, "\n")
            throw("memory reservation exceeds address space limit")
        }
    }

    if uintptr(v)&(heapArenaBytes-1) != 0 {
        throw("misrounded allocation in sysAlloc")
    }

    // Transition from Reserved to Prepared.
    // 轉換狀態將從預留到已準備。
    sysMap(v, size, &memstats.heap_sys)

mapped:
    // Create arena metadata.
    // 創建arena元數據。
    for ri := arenaIndex(uintptr(v)); ri <= arenaIndex(uintptr(v)+size-1); ri++ {
        l2 := h.arenas[ri.l1()]
        if l2 == nil {
            // Allocate an L2 arena map.
            // 分配L2arena映射。
            l2 = (*[1 << arenaL2Bits]*heapArena)(persistentalloc(unsafe.Sizeof(*l2), sys.PtrSize, nil))
            if l2 == nil {
                throw("out of memory allocating heap arena map")
            }
            atomic.StorepNoWB(unsafe.Pointer(&h.arenas[ri.l1()]), unsafe.Pointer(l2))
        }

        if l2[ri.l2()] != nil {
            throw("arena already initialized")
        }
        var r *heapArena
        r = (*heapArena)(h.heapArenaAlloc.alloc(unsafe.Sizeof(*r), sys.PtrSize, &memstats.gc_sys))
        if r == nil {
            r = (*heapArena)(persistentalloc(unsafe.Sizeof(*r), sys.PtrSize, &memstats.gc_sys))
            if r == nil {
                throw("out of memory allocating heap arena metadata")
            }
        }

        // Add the arena to the arenas list.
        // 將arena添加到arena列表中。
        if len(h.allArenas) == cap(h.allArenas) {
            size := 2 * uintptr(cap(h.allArenas)) * sys.PtrSize
            if size == 0 {
                size = physPageSize
            }
            newArray := (*notInHeap)(persistentalloc(size, sys.PtrSize, &memstats.gc_sys))
            if newArray == nil {
                throw("out of memory allocating allArenas")
            }
            oldSlice := h.allArenas
            *(*notInHeapSlice)(unsafe.Pointer(&h.allArenas)) = notInHeapSlice{newArray, len(h.allArenas), int(size / sys.PtrSize)}
            copy(h.allArenas, oldSlice)
            // Do not free the old backing array because
            // there may be concurrent readers. Since we
            // double the array each time, this can lead
            // to at most 2x waste.
            // 不要釋放舊的後備陣列,因爲可能有併發讀取器。
            // 由於我們每次將陣列加倍,因此最多可能導致2倍的浪費。
        }
        h.allArenas = h.allArenas[:len(h.allArenas)+1]
        h.allArenas[len(h.allArenas)-1] = ri

        // Store atomically just in case an object from the
        // new heap arena becomes visible before the heap lock
        // is released (which shouldn't happen, but there's
        // little downside to this).
        // 以原子方式存儲,以防新的堆空間中的對象在釋放堆鎖之前可見(這不應該發生,但這沒有什麼壞處)。
        atomic.StorepNoWB(unsafe.Pointer(&l2[ri.l2()]), unsafe.Pointer(r))
    }

    // Tell the race detector about the new heap memory.
    // 告訴競態檢測器新的堆內存。
    if raceenabled {
        racemapshadow(v, size)
    }

    return
}

// sysReserveAligned is like sysReserve, but the returned pointer is
// aligned to align bytes. It may reserve either n or n+align bytes,
// so it returns the size that was reserved.
/**
 * sysReserveAligned類似於sysReserve,但是返回的指針以字節對齊。
 * 它可以保留n個或n+align個字節,因此它返回保留的大小。
 * @param v 地址指針
 * @param size 待分配的字節數
 * @param align 對齊的字節數
 * @return unsafe.Pointer 新的地址指針
 * @return uintptr 分配的字節數
 **/
func sysReserveAligned(v unsafe.Pointer, size, align uintptr) (unsafe.Pointer, uintptr) {
    // Since the alignment is rather large in uses of this
    // function, we're not likely to get it by chance, so we ask
    // for a larger region and remove the parts we don't need.
    // 由於在使用此功能時對齊方式相當大,因此我們不太可能偶然得到它,
    // 因此我們要求更大的區域並刪除不需要的部分。
    retries := 0
retry:
    // 先進行未對齊內存保留
    p := uintptr(sysReserve(v, size+align))
    switch {
    case p == 0: // 未分配成功
        return nil, 0
    case p&(align-1) == 0:
        // We got lucky and got an aligned region, so we can
        // use the whole thing.
        // 我們很幸運或取到一個對齊區域,所以我們可以使用整個分配的內存。
        return unsafe.Pointer(p), size + align
    case GOOS == "windows":
        // On Windows we can't release pieces of a
        // reservation, so we release the whole thing and
        // re-reserve the aligned sub-region. This may race,
        // so we may have to try again.
        // 在Windows上,我們無法釋放部分保留內存,因此我們釋放整個內容並重新保留對齊的子區域。
        // 這可能會生產競爭,所以我們可能必須重試。
        sysFree(unsafe.Pointer(p), size+align, nil)
        p = alignUp(p, align)
        p2 := sysReserve(unsafe.Pointer(p), size)
        if p != uintptr(p2) {
            // Must have raced. Try again.
            // 必定競爭了,需要重試
            sysFree(p2, size, nil)
            if retries++; retries == 100 {
                throw("failed to allocate aligned heap memory; too many retries")
            }
            goto retry
        }
        // Success.
        return p2, size
    default:
        // Trim off the unaligned parts.
        // 修剪掉未對齊的部分。
        pAligned := alignUp(p, align)
        // 釋放[p, pAligned-1]的內存
        sysFree(unsafe.Pointer(p), pAligned-p, nil)
        end := pAligned + size
        endLen := (p + size + align) - end
        if endLen > 0 {
            // 釋放[end, endLen-1]的內存
            sysFree(unsafe.Pointer(end), endLen, nil)
        }
        // 返回分配地址和分配大小
        return unsafe.Pointer(pAligned), size
    }
}

// base address for all 0-byte allocations
// 所有0字節分配的基地址
var zerobase uintptr

// nextFreeFast returns the next free object if one is quickly available.
// Otherwise it returns 0.
/**
 * 如果一個可用的對象很快可用,則nextFreeFast返回下一個可用的對象。 否則返回0。
 * @param s 跨度對象
 * @return gclinkptr是指向gclink的指針,但對垃圾收集器不透明。
 **/
func nextFreeFast(s *mspan) gclinkptr {
    theBit := sys.Ctz64(s.allocCache) // Is there a free object in the allocCache?
    if theBit < 64 {
        result := s.freeindex + uintptr(theBit)
        if result < s.nelems {
            freeidx := result + 1
            if freeidx%64 == 0 && freeidx != s.nelems {
                return 0
            }
            s.allocCache >>= uint(theBit + 1)
            s.freeindex = freeidx
            s.allocCount++
            return gclinkptr(result*s.elemsize + s.base())
        }
    }
    return 0
}

// nextFree returns the next free object from the cached span if one is available.
// Otherwise it refills the cache with a span with an available object and
// returns that object along with a flag indicating that this was a heavy
// weight allocation. If it is a heavy weight allocation the caller must
// determine whether a new GC cycle needs to be started or if the GC is active
// whether this goroutine needs to assist the GC.
//
// Must run in a non-preemptible context since otherwise the owner of
// c could change.
/**
 * nextFree從緩存範圍中返回下一個空閒對象(如果有)。 否則,它將使用可用對象的跨度重新填充高速緩存,
 * 並返回該對象以及指示這是繁重分配的標誌。 如果分配很重,則調用方必須確定是否需要啓動新的GC週期,
 * 或者GC是否處於活動狀態,因此該例行程序是否需要協助GC。
 *
 * 必須在不可搶佔的上下文中運行,因爲否則c的所有者可能會更改。
 * @param spc spanClass表示跨度的大小類別和noscan-ness
 * @return v gclinkptr指針
 * @return s 跨度指針
 * @return shouldhelpgc 是否需要gc幫助
 **/
func (c *mcache) nextFree(spc spanClass) (v gclinkptr, s *mspan, shouldhelpgc bool) {
    s = c.alloc[spc]
    shouldhelpgc = false
    freeIndex := s.nextFreeIndex()
    if freeIndex == s.nelems {
        // The span is full.
        // 跨度已滿
        if uintptr(s.allocCount) != s.nelems {
            println("runtime: s.allocCount=", s.allocCount, "s.nelems=", s.nelems)
            throw("s.allocCount != s.nelems && freeIndex == s.nelems")
        }
        // 重新填充跨度
        c.refill(spc)
        // 標記需要gc幫助
        shouldhelpgc = true
        s = c.alloc[spc]

        freeIndex = s.nextFreeIndex()
    }

    if freeIndex >= s.nelems {
        throw("freeIndex is not valid")
    }

    v = gclinkptr(freeIndex*s.elemsize + s.base())
    s.allocCount++
    if uintptr(s.allocCount) > s.nelems {
        println("s.allocCount=", s.allocCount, "s.nelems=", s.nelems)
        throw("s.allocCount > s.nelems")
    }
    return
}

// Allocate an object of size bytes.
// Small objects are allocated from the per-P cache's free lists.
// Large objects (> 32 kB) are allocated straight from the heap.
/**
 * 分配一個大小爲size字節的對象。
 * 從每個P緩存的空閒列表中分配小對象。
 * 從堆直接分配大對象(> 32 kB)。
 * @param size 分配的內存
 * @param needzero
 * @return 分配的地址指針
 **/
func mallocgc(size uintptr, typ *_type, needzero bool) unsafe.Pointer {
    if gcphase == _GCmarktermination {
        // _GCmarktermination: GC標記終止:分配黑色,P幫助GC,寫屏障啓用
        throw("mallocgc called with gcphase == _GCmarktermination")
    }

    // 空地址
    if size == 0 {
        return unsafe.Pointer(&zerobase)
    }

    if debug.sbrk != 0 {
        align := uintptr(16)
        if typ != nil {
            // TODO(austin): This should be just
            //   align = uintptr(typ.align)
            // but that's only 4 on 32-bit platforms,
            // even if there's a uint64 field in typ (see #599).
            // This causes 64-bit atomic accesses to panic.
            // Hence, we use stricter alignment that matches
            // the normal allocator better.
            // TODO(austin)):這應該只是align = uintptr(typ.align),
            // 但即使在typ中有uint64字段,它在32位平臺上也只有4(請參閱#599)。
            // 這會導致對64位原子訪問出現panic。 因此,我們使用更嚴格的對齊方式來更好地匹配常規分配器。
            if size&7 == 0 {
                align = 8
            } else if size&3 == 0 {
                align = 4
            } else if size&1 == 0 {
                align = 2
            } else {
                align = 1
            }
        }
        return persistentalloc(size, align, &memstats.other_sys)
    }

    // assistG is the G to charge for this allocation, or nil if
    // GC is not currently active.
    // assistantG是負責此次分配的G,如果GC當前未處於活動狀態,則爲nil。
    var assistG *g
    // 如果允許增變輔助和後臺標記工作程序將對象變黑,則gcBlackenEnabled爲1。
    // 僅當gcphase == _GCmark時纔可以設置。
    if gcBlackenEnabled != 0 {
        // Charge the current user G for this allocation.
        // 獲取負責此次分配的g
        assistG = getg()
        if assistG.m.curg != nil {
            assistG = assistG.m.curg
        }
        // Charge the allocation against the G. We'll account
        // for internal fragmentation at the end of mallocgc.
        // 我們將在mallocgc末尾統計內部碎片。
        assistG.gcAssistBytes -= int64(size)

        if assistG.gcAssistBytes < 0 {
            // This G is in debt. Assist the GC to correct
            // this before allocating. This must happen
            // before disabling preemption.
            // 這個G負債中。 協助GC進行更正,然後再分配。 這必須在禁用搶佔之前發生。
            gcAssistAlloc(assistG)
        }
    }

    // Set mp.mallocing to keep from being preempted by GC.
    // Set mp.mallocing to keep from being preempted by GC.
    mp := acquirem()
    if mp.mallocing != 0 { // 當前m已經在分配內容了
        throw("malloc deadlock")
    }

    // 處理信號的g和當前的g是同一個
    if mp.gsignal == getg() {
        throw("malloc during signal")
    }
    mp.mallocing = 1 // 設當前m正在分配內存

    shouldhelpgc := false
    dataSize := size
    c := gomcache()
    var x unsafe.Pointer
    noscan := typ == nil || typ.ptrdata == 0
    if size <= maxSmallSize { // 小分配
        if noscan && size < maxTinySize { // 微小分配
            // Tiny allocator.
            //
            // Tiny allocator combines several tiny allocation requests
            // into a single memory block. The resulting memory block
            // is freed when all subobjects are unreachable. The subobjects
            // must be noscan (don't have pointers), this ensures that
            // the amount of potentially wasted memory is bounded.
            //
            // Size of the memory block used for combining (maxTinySize) is tunable.
            // Current setting is 16 bytes, which relates to 2x worst case memory
            // wastage (when all but one subobjects are unreachable).
            // 8 bytes would result in no wastage at all, but provides less
            // opportunities for combining.
            // 32 bytes provides more opportunities for combining,
            // but can lead to 4x worst case wastage.
            // The best case winning is 8x regardless of block size.
            //
            // Objects obtained from tiny allocator must not be freed explicitly.
            // So when an object will be freed explicitly, we ensure that
            // its size >= maxTinySize.
            //
            // SetFinalizer has a special case for objects potentially coming
            // from tiny allocator, it such case it allows to set finalizers
            // for an inner byte of a memory block.
            //
            // The main targets of tiny allocator are small strings and
            // standalone escaping variables. On a json benchmark
            // the allocator reduces number of allocations by ~12% and
            // reduces heap size by ~20%.
            //
            // 微小分配器
            //
            // 微小分配器 將幾個微小的分配請求組合到一個內存塊中。當所有子對象均不可訪問時,將釋放結果存儲塊。
            // 子對象必須是noscan(沒有指針),以確保限制可能浪費的內存量。
            //
            // 用於合併的存儲塊的大小(maxTinySize)是可調的。當前設置爲16個字節,這涉及最壞情況下2倍的內存浪費
            // (當除了一個子對象之外的所有其他對象均無法訪問時)。8字節完全不會浪費,但是合併的機會更少。 3
            // 2字節提供了更多的合併機會,但可能導致最壞情況下4倍浪費。無論塊大小如何,最佳案例獲勝都是8倍。
            //
            // 不能顯式釋放從微小分配器獲得的對象。因此,當明確釋放對象時,我們確保其大小>=maxTinySize。
            //
            // SetFinalizer對於可能來自微小分配器的對象具有特殊情況,在這種情況下,它可以爲內存塊的內部字節設置終結器(finalizers)。
            //
            // 微小分配器的主要目標是小字符串和獨立的轉義變量。在json基準上,分配器將分配數量減少了約12%,並將堆大小減少了約20%。
            off := c.tinyoffset
            // Align tiny pointer for required (conservative) alignment.
            // 對齊微型指針以進行必需的(保守的)對齊。
            if size&7 == 0 {
                off = alignUp(off, 8)
            } else if size&3 == 0 {
                off = alignUp(off, 4)
            } else if size&1 == 0 {
                off = alignUp(off, 2)
            }
            if off+size <= maxTinySize && c.tiny != 0 {
                // The object fits into existing tiny block.
                // 該對象適合現有的微小塊。
                x = unsafe.Pointer(c.tiny + off)
                c.tinyoffset = off + size
                c.local_tinyallocs++
                mp.mallocing = 0
                releasem(mp)
                return x
            }
            // Allocate a new maxTinySize block.
            // 分配一個新的maxTinySize塊。
            span := c.alloc[tinySpanClass]
            v := nextFreeFast(span)
            if v == 0 { // 快速分配沒有成功,進行通常規分配
                v, _, shouldhelpgc = c.nextFree(tinySpanClass)
            }
            x = unsafe.Pointer(v)
            (*[2]uint64)(x)[0] = 0
            (*[2]uint64)(x)[1] = 0
            // See if we need to replace the existing tiny block with the new one
            // based on amount of remaining free space.
            // 根據剩餘的可用空間量,看看是否需要用新的小塊替換現有的小塊。
            if size < c.tinyoffset || c.tiny == 0 {
                c.tiny = uintptr(x)
                c.tinyoffset = size
            }
            size = maxTinySize
        } else { // 小分配置
            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)
            span := c.alloc[spc]
            v := nextFreeFast(span)
            if v == 0 {
                v, span, shouldhelpgc = c.nextFree(spc)
            }
            x = unsafe.Pointer(v)
            if needzero && span.needzero != 0 {
                memclrNoHeapPointers(unsafe.Pointer(v), size)
            }
        }
    } else { // 大分配
        var s *mspan
        shouldhelpgc = true // 需要gc幫助
        // systemstack在系統堆棧上運行fn。
        systemstack(func() {
            s = largeAlloc(size, needzero, noscan)
        })
        s.freeindex = 1
        s.allocCount = 1
        x = unsafe.Pointer(s.base())
        size = s.elemsize
    }

    var scanSize uintptr
    if !noscan {
        // If allocating a defer+arg block, now that we've picked a malloc size
        // large enough to hold everything, cut the "asked for" size down to
        // just the defer header, so that the GC bitmap will record the arg block
        // as containing nothing at all (as if it were unused space at the end of
        // a malloc block caused by size rounding).
        // The defer arg areas are scanned as part of scanstack.
        // 如果分配一個defer+arg塊,現在我們已經選擇了一個足夠大的malloc大小來容納所有內容,
        // 請將“asked for”大小減小爲defer頭部,以便GC位圖將arg塊記錄爲不包含任何內容
        // (好像是由於大小舍入導致的malloc塊末尾的未使用空間)。
        // 延遲arg區域將作爲scanstack的一部分進行掃描。
        if typ == deferType {
            dataSize = unsafe.Sizeof(_defer{})
        }
        // 記錄數據類型
        heapBitsSetType(uintptr(x), size, dataSize, typ)
        if dataSize > typ.size {
            // Array allocation. If there are any
            // pointers, GC has to scan to the last
            // element.
            // 數組分配。 如果有任何指針,GC必須掃描到最後一個元素。
            if typ.ptrdata != 0 {
                scanSize = dataSize - typ.size + typ.ptrdata
            }
        } else {
            scanSize = typ.ptrdata
        }
        c.local_scan += scanSize
    }

    // Ensure that the stores above that initialize x to
    // type-safe memory and set the heap bits occur before
    // the caller can make x observable to the garbage
    // collector. Otherwise, on weakly ordered machines,
    // the garbage collector could follow a pointer to x,
    // but see uninitialized memory or stale heap bits.
    // 確保在調用者使x對垃圾收集器可觀察之前,將上面的x初始化爲類型安全的內存並設置堆存儲的位信息。
    // 否則,在順序較弱的計算機上,垃圾收集器可能會跟隨指向x的指針,但會看到未初始化的內存或陳舊的堆位。
    publicationBarrier()

    // Allocate black during GC.
    // All slots hold nil so no scanning is needed.
    // This may be racing with GC so do it atomically if there can be
    // a race marking the bit.
    // 在GC期間分配黑色。 所有槽位均爲nill,因此無需掃描。這可能與GC發生爭用,
    // 因此如果可以進行爭奪來標記該位,則可以自動進行。
    if gcphase != _GCoff { // go未運行
        // gcmarknewobject將新分配的對象標記爲黑色。obj不得包含任何非nil指針。
        gcmarknewobject(uintptr(x), size, scanSize)
    }

    if raceenabled {
        racemalloc(x, size)
    }

    if msanenabled {
        msanmalloc(x, size)
    }

    mp.mallocing = 0 // 標記分配已經完成
    releasem(mp) // 釋放m

    if debug.allocfreetrace != 0 {
        tracealloc(x, size, typ)
    }

    // 設置內存採樣
    if rate := MemProfileRate; rate > 0 {
        if rate != 1 && size < c.next_sample {
            c.next_sample -= size
        } else {
            mp := acquirem()
            profilealloc(mp, x, size)
            releasem(mp)
        }
    }

    if assistG != nil {
        // Account for internal fragmentation in the assist
        // debt now that we know it.
        // 在assist debt中解決內部碎片化問題
        assistG.gcAssistBytes -= int64(size - dataSize)
    }

    if shouldhelpgc {
        // gcTrigger是用於啓動GC循環的動作(predicate)。 具體來說,它是_GCoff階段的退出條件。
        if t := (gcTrigger{kind: gcTriggerHeap}); t.test() {
            gcStart(t)
        }
    }

    return x
}

/**
 * 大內存分配
 * @param
 * @return
 **/
func largeAlloc(size uintptr, needzero bool, noscan bool) *mspan {
    // print("largeAlloc size=", size, "\n")
    // 判斷是否內存溢出
    if size+_PageSize < size {
        throw("out of memory")
    }

    // 計算需要分配的頁數
    npages := size >> _PageShift
    if size&_PageMask != 0 {
        npages++
    }

    // Deduct credit for this span allocation and sweep if
    // necessary. mHeap_Alloc will also sweep npages, so this only
    // pays the debt down to npage pages.
    // 扣除此跨度分配的信用,並在必要時進行掃描。 mHeap_Alloc還將清掃npages,因此這隻會將債務減少到npage頁。
    deductSweepCredit(npages*_PageSize, npages)

    // mheap_.alloc從GC的堆中分配新的npage頁。
    s := mheap_.alloc(npages, makeSpanClass(0, noscan), needzero)
    if s == nil {
        throw("out of memory")
    }
    s.limit = s.base() + size
    // heapBitsForAddr返回地址addr的heapBits。
    heapBitsForAddr(s.base()).initSpan(s)
    return s
}

// implementation of new builtin
// compiler (both frontend and SSA backend) knows the signature
// of this function
/**
 * 內建函數new的實現,編譯器(前端和SSA後端)都知道此函數的簽名
 * @param 類型
 * @return 對象指針
 **/
func newobject(typ *_type) unsafe.Pointer {
    return mallocgc(typ.size, typ, true)
}

/**
 * reflect包中的unsafe_New實現
 * @param
 * @return
 **/
//go:linkname reflect_unsafe_New reflect.unsafe_New
func reflect_unsafe_New(typ *_type) unsafe.Pointer {
    return mallocgc(typ.size, typ, true)
}

/**
 * internal/reflectlite包中的unsafe_New實現
 * @param
 * @return
 **/
//go:linkname reflectlite_unsafe_New internal/reflectlite.unsafe_New
func reflectlite_unsafe_New(typ *_type) unsafe.Pointer {
    return mallocgc(typ.size, typ, true)
}

/**
 * newarray分配一個類型爲typ的n個元素組成的數組。
 * @param
 * @return
 **/
// newarray allocates an array of n elements of type typ.
func newarray(typ *_type, n int) unsafe.Pointer {
    if n == 1 {
        return mallocgc(typ.size, typ, true)
    }
    // 計算數組創建需要分配的內存
    mem, overflow := math.MulUintptr(typ.size, uintptr(n))
    if overflow || mem > maxAlloc || n < 0 {
        panic(plainError("runtime: allocation size out of range"))
    }
    return mallocgc(mem, typ, true)
}

/**
 * reflect包中的unsafe_NewArray實現
 * @param
 * @return
 **/
//go:linkname reflect_unsafe_NewArray reflect.unsafe_NewArray
func reflect_unsafe_NewArray(typ *_type, n int) unsafe.Pointer {
    return newarray(typ, n)
}

func profilealloc(mp *m, x unsafe.Pointer, size uintptr) {
    mp.mcache.next_sample = nextSample()
    mProf_Malloc(x, size)
}

// nextSample returns the next sampling point for heap profiling. The goal is
// to sample allocations on average every MemProfileRate bytes, but with a
// completely random distribution over the allocation timeline; this
// corresponds to a Poisson process with parameter MemProfileRate. In Poisson
// processes, the distance between two samples follows the exponential
// distribution (exp(MemProfileRate)), so the best return value is a random
// number taken from an exponential distribution whose mean is MemProfileRate.
/**
 * nextSample返回用於堆分析的下一個採樣點。 目標是在每個MemProfileRate字節抽樣平均分配,
 * 但在分配時間軸上具有完全隨機的分配; 這對應於帶有參數MemProfileRate的泊松過程。
 * 在泊松過程中,兩個樣本之間的距離遵循指數分佈(exp(MemProfileRate)),
 * 因此最佳返回值是取自均值爲MemProfileRate的指數分佈的隨機數。
 * @param
 * @return
 **/
func nextSample() uintptr {
    if GOOS == "plan9" {
        // Plan 9 doesn't support floating point in note handler.
        if g := getg(); g == g.m.gsignal {
            return nextSampleNoFP()
        }
    }

    return uintptr(fastexprand(MemProfileRate))
}

// fastexprand returns a random number from an exponential distribution with
// the specified mean.
/**
 * fastexprand從具有指定平均值的指數分佈中返回一個隨機數。
 * @param
 * @return
 **/
func fastexprand(mean int) int32 {
    // Avoid overflow. Maximum possible step is
    // -ln(1/(1<<randomBitCount)) * mean, approximately 20 * mean.
    // 避免溢出。 最大可能步長爲-ln(1/(1<<randomBitCount))*平均值,大約20*平均值。
    switch {
    case mean > 0x7000000:
        mean = 0x7000000
    case mean == 0:
        return 0
    }

    // Take a random sample of the exponential distribution exp(-mean*x).
    // The probability distribution function is mean*exp(-mean*x), so the CDF is
    // p = 1 - exp(-mean*x), so
    // q = 1 - p == exp(-mean*x)
    // log_e(q) = -mean*x
    // -log_e(q)/mean = x
    // x = -log_e(q) * mean
    // x = log_2(q) * (-log_e(2)) * mean    ; Using log_2 for efficiency
    const randomBitCount = 26
    q := fastrand()%(1<<randomBitCount) + 1
    qlog := fastlog2(float64(q)) - randomBitCount
    if qlog > 0 {
        qlog = 0
    }
    const minusLog2 = -0.6931471805599453 // -ln(2)
    return int32(qlog*(minusLog2*float64(mean))) + 1
}

// nextSampleNoFP is similar to nextSample, but uses older,
// simpler code to avoid floating point.
/**
 * nextSampleNoFP與nextSample相似,但使用更舊,更簡單的代碼來避免浮點數。
 * @param
 * @return
 **/
func nextSampleNoFP() uintptr {
    // Set first allocation sample size.
    rate := MemProfileRate
    if rate > 0x3fffffff { // make 2*rate not overflow
        rate = 0x3fffffff
    }
    if rate != 0 {
        return uintptr(fastrand() % uint32(2*rate))
    }
    return 0
}

/**
 * 持久化分配結構
 **/
type persistentAlloc struct {
    base *notInHeap
    off  uintptr
}

/**
 * 全局分配結構
 **/
var globalAlloc struct {
    mutex
    persistentAlloc
}

// persistentChunkSize is the number of bytes we allocate when we grow
// a persistentAlloc.
// persistentChunkSize是我們增長persistentAlloc時分配的字節數。256KB
const persistentChunkSize = 256 << 10

// persistentChunks is a list of all the persistent chunks we have
// allocated. The list is maintained through the first word in the
// persistent chunk. This is updated atomically.
// psistenceChunks是我們分配的所有持久性塊的列表。
// 該列表通過持久性塊中的第一個字(word)進行維護。 這是原子更新的。
var persistentChunks *notInHeap

// Wrapper around sysAlloc that can allocate small chunks.
// There is no associated free operation.
// Intended for things like function/type/debug-related persistent data.
// If align is 0, uses default align (currently 8).
// The returned memory will be zeroed.
//
// Consider marking persistentalloc'd types go:notinheap.
/**
 * 圍繞sysAlloc的包裝程序,可以分配小內存塊。 沒有相關的自由操作。
 * 用於函數/類型/調試相關的持久數據。 如果align爲0,則使用默認的align(當前爲8)。
 * 返回的內存將被清零。考慮標記爲持久分配的類型go:notinheap。
 * @param 
 * @return 
 **/
func persistentalloc(size, align uintptr, sysStat *uint64) unsafe.Pointer {
    var p *notInHeap
    systemstack(func() {
        p = persistentalloc1(size, align, sysStat)
    })
    return unsafe.Pointer(p)
}

/**
 * 必須在系統堆棧上運行,因爲堆棧增長可以(重新)調用它。 請參閱問題9174。
 * @param
 * @return
 **/
// Must run on system stack because stack growth can (re)invoke it.
// See issue 9174.
//go:systemstack
func persistentalloc1(size, align uintptr, sysStat *uint64) *notInHeap {
    const (
        // Windows上的VM預留粒度爲64K
        maxBlock = 64 << 10 // VM reservation granularity is 64K on windows
    )

    if size == 0 {
        throw("persistentalloc: size == 0")
    }
    if align != 0 {
        if align&(align-1) != 0 {
            throw("persistentalloc: align is not a power of 2")
        }
        if align > _PageSize {
            throw("persistentalloc: align is too large")
        }
    } else {
        align = 8
    }

    if size >= maxBlock {
        return (*notInHeap)(sysAlloc(size, sysStat))
    }

    mp := acquirem()
    var persistent *persistentAlloc
    if mp != nil && mp.p != 0 {
        persistent = &mp.p.ptr().palloc
    } else {
        lock(&globalAlloc.mutex)
        persistent = &globalAlloc.persistentAlloc
    }
    persistent.off = alignUp(persistent.off, align)
    if persistent.off+size > persistentChunkSize || persistent.base == nil {
        persistent.base = (*notInHeap)(sysAlloc(persistentChunkSize, &memstats.other_sys))
        if persistent.base == nil {
            if persistent == &globalAlloc.persistentAlloc {
                unlock(&globalAlloc.mutex)
            }
            throw("runtime: cannot allocate memory")
        }

        // Add the new chunk to the persistentChunks list.
        for {
            chunks := uintptr(unsafe.Pointer(persistentChunks))
            *(*uintptr)(unsafe.Pointer(persistent.base)) = chunks
            if atomic.Casuintptr((*uintptr)(unsafe.Pointer(&persistentChunks)), chunks, uintptr(unsafe.Pointer(persistent.base))) {
                break
            }
        }
        persistent.off = alignUp(sys.PtrSize, align)
    }
    p := persistent.base.add(persistent.off)
    persistent.off += size
    releasem(mp)
    if persistent == &globalAlloc.persistentAlloc {
        unlock(&globalAlloc.mutex)
    }

    if sysStat != &memstats.other_sys {
        mSysStatInc(sysStat, size)
        mSysStatDec(&memstats.other_sys, size)
    }
    return p
}

/**
 * inPersistentAlloc報告p是否指向由persistentalloc分配的內存。
 * 由於它是由cgo檢查器代碼調用的,而後者由寫屏障代碼調用,因此必須不能拆分。
 * @param
 * @return
 **/
// inPersistentAlloc reports whether p points to memory allocated by
// persistentalloc. This must be nosplit because it is called by the
// cgo checker code, which is called by the write barrier code.
//go:nosplit
func inPersistentAlloc(p uintptr) bool {
    chunk := atomic.Loaduintptr((*uintptr)(unsafe.Pointer(&persistentChunks)))
    for chunk != 0 {
        if p >= chunk && p < chunk+persistentChunkSize {
            return true
        }
        chunk = *(*uintptr)(unsafe.Pointer(chunk))
    }
    return false
}

// linearAlloc is a simple linear allocator that pre-reserves a region
// of memory and then maps that region into the Ready state as needed. The
// caller is responsible for locking.
/**
 * linearAlloc是一個簡單的線性分配器,它可以預先保留一個內存區域,
 * 然後根據需要將該區域映射到“就緒”狀態。 調用方負責鎖定。
 **/
type linearAlloc struct {
    next   uintptr // next free byte // next free byte
    mapped uintptr // one byte past end of mapped space // 映射空間的末尾一個字節
    end    uintptr // end of reserved space // 保留空間的結尾
}

/**
 * 初始化線性分配器
 * @param
 * @return
 **/
func (l *linearAlloc) init(base, size uintptr) {
    l.next, l.mapped = base, base
    l.end = base + size
}

/**
 * 分配內存
 * @param
 * @return
 **/
func (l *linearAlloc) alloc(size, align uintptr, sysStat *uint64) unsafe.Pointer {
    p := alignUp(l.next, align)
    if p+size > l.end {
        return nil
    }
    l.next = p + size
    if pEnd := alignUp(l.next-1, physPageSize); pEnd > l.mapped {
        // Transition from Reserved to Prepared to Ready.
        sysMap(unsafe.Pointer(l.mapped), pEnd-l.mapped, sysStat)
        sysUsed(unsafe.Pointer(l.mapped), pEnd-l.mapped)
        l.mapped = pEnd
    }
    return unsafe.Pointer(p)
}

/**
 * notInHeap是由sysAlloc或persistentAlloc之類的較低級分配器分配的堆外內存。
 *
 * 通常,最好使用標記爲go:notinheap的實類型,但這在不可能的情況下(例如在分配器中)用作通用類型。
 *
 * TODO:使用它作爲sysAlloc,persistentAlloc等的返回類型嗎?
 **/
// notInHeap is off-heap memory allocated by a lower-level allocator
// like sysAlloc or persistentAlloc.
//
// In general, it's better to use real types marked as go:notinheap,
// but this serves as a generic type for situations where that isn't
// possible (like in the allocators).
//
// TODO: Use this as the return type of sysAlloc, persistentAlloc, etc?
//
//go:notinheap
type notInHeap struct{}

func (p *notInHeap) add(bytes uintptr) *notInHeap {
    return (*notInHeap)(unsafe.Pointer(uintptr(unsafe.Pointer(p)) + bytes))
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章