golang系列之-内存分配

golang的内存分配机制最初是基于TCMalloc,演化至今已经有了很大差异。其原理是:slab + tiling algorithm + 层级内存分配。本文仅介绍如何通过mallocgc分配内存,不涉及栈内存分配管理、手动内存管理等内容。当前go版本:1.24

前言

slab + tiling

内存分配基本单位-mspan(即slab),内部再划分更小的块(即tiling)

小对象(<=32KB)按预定义的sizeclass分成不同的mspan,每个mspan最低有8KB,切割成指定大小的块。而大对象(>32KB)的sizeclass=0,不限制大小,mspan不分块。如下

1
2
3
4
5
6
7
8
// mspann -> sizeclass=0  -> nKB  -> | <-----   nKB   -----> |
// ...
// mspanx -> sizeclass=1 -> 8KB -> | 8B | 8B | ... | 8B |
// ...
// mspany -> sizeclass=5 -> 8KB -> | 48B | 48B | ... | 48B |
// ...
// mspanz -> sizeclass=65 -> 80KB
// ...

make([]int, 5),创建一块40B大小的内存区域,经过计算这个内存块会从sizeclass=5的mspan分配(该mspan的每个元素大小为48B)

层级内存分配

参考TCMalloc,内存分配时主要分为3级:mcache、mcentral、mheap

  1. mcache为线程缓存,每个p都有一个,所有的内存分配/回收都是先通过mcache,访问不需要加锁
  2. mcentral负责向mheap申请分配内存、管理mspan,按spanclass分组,嵌入到mheap结构体,全局唯一
  3. mheap负责从系统申请内存进行分配管理,一般情况下访问都要加锁,全局唯一

执行mallocgc分配内存时,mheap、mcentral、mcache的关系如下

1
2
3
4
5
6
7
//  mallocgc
// |
// | (<=32KB)
// |--> p.mcache.alloc[spanclass] --> mheap.central[spanclass] --> mheap
// |
// | (>32KB)
// |--> mheap
  • 如果对象<=32KB
    • 从p的mcache找一个空闲的mspan分配一段内存/地址空间
    • 如果mspan没有空闲空间,从mcentral申请一个新的/重用一个已清扫的mspan
    • 如果mcentral也没有可用mspan,通过mheap跟OS申请一段内存,初始化一个mspan返回
  • 如果对象>32KB
    • 通过mheap跟OS申请一段内存,初始化一个mspan返回

系统内存状态

这里需要了解系统内存的几个状态,以及go内部是如何从系统分配、释放内存的

状态 含义
None 默认状态,未映射,地址空间未被保留或使用
Reserved 已保留,但未提交,即地址空间已经被申请,但尚未向操作系统请求实际物理内存
Prepared 已提交但未使用,已经向操作系统申请了物理内存,但可能未完全初始化
Ready 可用状态,内存已初始化,可用于分配

状态转换函数

函数 主要作用 是否分配物理内存 是否映射虚拟内存 内存状态转换 备注
sysAlloc 直接申请内存 ✅ 是 ✅ 是 None -> Ready 可能会触发 mmap
sysReserve 保留虚拟地址空间,但不映射 ❌ 否 ✅ 是 None -> Reserved 预留地址,后续 sysMap
sysMap 将预留的虚拟地址映射为实际物理内存 ✅ 是 ✅ 是 Reserved -> Prepared 只有 sysReserve 过的地址能 sysMap
sysUsed 标记某段地址正在使用 ✅ 是 ✅ 是 Prepared -> Ready 可能会触发 madvise 让物理页生效
sysUnused 标记某段地址未使用,可以回收物理内存 ⚠️ 可能 ❌ 否 Ready -> Prepared MADV_DONTNEED,内存仍属于进程
sysFault 让一段地址变成不可访问 ❌ 否 ✅ 是 Ready -> Reserved mprotect(PROT_NONE),用于调试
sysFree 释放虚拟内存,归还给 OS ✅ 是 ✅ 是 -> None munmap,这段内存不能再用

我对Go内存分配的理解

  1. 内存管理的本质是地址空间的组织和维护
  2. Go的内存策略:尽量保留虚拟地址,按需释放物理页
  3. Go会一次性申请64MB内存(Reserved-虚拟地址空间),但并不是立即分配物理页,而是在需要分配时使用sysMap + sysUsed使一小部份内存可用(Reserved->Prepared->Ready)

GC助攻

为了防止内存分配速度过快,导致GC跟不上,分配时会判断是否需要协助GC标记/清扫。每次都要判断是否需要协助标记,而清扫只发生在获取新的mspan和大对象分配场景下

数据结构

mspan

mspan负责纪录一片连续内存区域的起始/终止地址、组织信息等,实际可用内存地址需要通过页分配器获得。一个mspan最少可管理8KB的内存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
type mspan struct {
_ sys.NotInHeap
next *mspan // 链表用
prev *mspan // 链表用
list *mSpanList // debug,忽略

startAddr uintptr // 连续内存区域起始地址,同base
npages uintptr // 页数量,每个页8KB,每个mspan最少有1个页

manualFreeList gclinkptr // 空闲对象链表,状态为mSpanManual时用

freeindex uint16 // 下一个可用对象索引,范围[0,nelems),GC后重置为0
nelems uint16 // 可存储对象总量,=n*8192/elemsize
freeIndexForScan uint16 // 同freeindex,用于GC

allocCache uint64 // 64位bitmap,用于快速分配,从allocBits获取填充

allocBits *gcBits // bitmap,用于标记mspan内哪些对象已被使用
gcmarkBits *gcBits // bitmap,用于GC时将对象置为黑色
pinnerBits *gcBits //

// 如果sweepgen == h->sweepgen - 2,mspan需要被清扫
// 如果sweepgen == h->sweepgen - 1,mspan正在被清扫
// 如果sweepgen == h->sweepgen,mspan已被清扫,可以随时被使用
// 如果sweepgen == h->sweepgen + 1,mspan在sweep启动前被mcache使用,当然仍被使用,需要清扫
// 如果sweepgen == h->sweepgen + 3,mspan已被清扫并被mcache使用
sweepgen uint32 // 版本计数器,使用时/GC时同步mheap.sweepgen
divMul uint32 // 用于优化除法运算
allocCount uint16 // 已分配对象数,范围[0,nelems]
spanclass spanClass // 类别,由7位sizeclass和1位noscan组成
state mSpanStateBox // 状态,0-默认 1-使用中(heap) 2-使用中(手动)
needzero uint8 // 分配前是否需要清0
isUserArenaChunk bool // 归属于arena包,用户手动管理
allocCountBeforeCache uint16 // allocCount快照,统计用
elemsize uintptr // 对象大小
limit uintptr // 终止地址,与startAddr搭配使用
speciallock mutex //
specials *special //
userArenaChunkFree addrRange //
largeType *_type // 数据类型,大对象(>32KB)分配时纪录,为nil表示noscan
}

scan/noscan

noscan:如果对象是nil或者对象不包含指针(scalar)
scan:对象包含指针,如一个结构体有字段类型是指针类型

状态

mspan状态列表如下

name value description
mSpanDead 0 默认状态,未使用/回收
mSpanInUse 1 使用中,由go自己管理
mSpanManual 2 使用中,手动管理,一般用于栈分配

类型

name value description
spanAllocHeap 0 默认,heap类型,其他均为手动分配
spanAllocStack 1 stack类型
spanAllocPtrScalarBits 2 GC bitmap
spanAllocWorkBuf 3 写屏障缓冲

小对象mspan示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 只有1个page的小对象(<=32KB)mspan示例如下
//
// noscan: startAddr
// |
// v
// | <---------- 8192 bytes ----------> |
// | free |
// | 8192 bytes |
//
// scan && size<=512B
// |
// v
// | <---------- 8192 bytes ----------> |
// | free | heapBits |
// | 8064 bytes | 128 bytes | -> 用于标记对象的可达性
//
//
// scan类型时,elem大小超过512B需要存储类型,如下
//
// <= 512B => elem no header => | elemsize | ... | elemsize |
// > 512B => elem with header => |8 + elemsize | ... | 8 + elemsize |
//
// 大对象的类型存储在mspan的largeType字段

mcache

每个p都有一个mcache,负责当前线程的内存分配,超小对象(scalar)通过tiny分配器分配(优化,本质还是访问的是mspan),小对象则通过找到alloc数组里相应的mspan进行分配,不负责大对象的分配

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
type mcache struct {
_ sys.NotInHeap

// 下面三个每次malloc都会访问
nextSample int64 // 分配nextSample字节内存后触发采样
memProfRate int // 内存采样频率(缓存)
scanAlloc uintptr // 需要被GC扫描的字节数(跟分配的内存大小也差不了多少)

// tiny分配器(spanclass=5 => sizeclass=2 + noscan)
tiny uintptr // 16字节内存区域
tinyoffset uintptr // 偏移量/下一个可分配地址
tinyAllocs uintptr // 通过tiny区域分配的对象数量

alloc [numSpanClasses]*mspan // 分配<=32KB时使用,共136个mspan指针
stackcache [_NumStackOrders]stackfreelist // 栈分配使用,linux下共4个栈缓存链表
// 只有当flushGen==sweepgen-2时清扫mcache
flushGen atomic.Uint32 // sweepgen快照,创建mcache以及gcStart时同步
}

alloc是一个固定长度的数组,包含136个mspan指针。如果其中的mspan空间分配完/不足以容纳新对象,则向mcentral申请一个新的/清扫过的mspan替换原mspan后重试

mcentral

新的mspan会立即替换mcache的纪录,而mcentral负责纪录空间已满/不足的mspan、清扫重用mspan/向mheap申请分配新的mspan。按spanclass进行分组,每个组都是一个二维数组,嵌入到mheap结构体,全局唯一

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
type mcentral struct {
_ sys.NotInHeap

spanclass spanClass // 类别,由7位的sizeclass和1位的noscan组成

partial [2]spanSet // mspan空间还有剩余(但不足以申请新的对象)
full [2]spanSet // mspan空间完全用完
}

// 数组第一维
type spanSet struct {
spineLock mutex // 锁
spine atomicSpanSetSpinePointer // spanSetBlock切片指针,指向*[N]atomic.Pointer[spanSetBlock],最少有256个指针
spineLen atomic.Uintptr // spine数组长度
spineCap uintptr // spine数组容量,访问需要加锁

// 每个mspan的内存最小为1个页即8KB,2^32个索引最少能代表32TB
index atomicHeadTailIndex // 索引,分为高32位-head,低32位-tail
}

// 数组第二维
type spanSetBlock struct {
lfnode // 无锁栈
popped atomic.Uint32 // 计数器,计算pop次数
spans [spanSetBlockEntries]atomicMSpanPointer // mspan指针数组,共512个指针
}

// 无锁栈
type lfnode struct {
next uint64 // 元素指针
pushcnt uintptr // 总量
}

mcentral示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//
// mcentral[0].spanclass = 0
// .partial[0] -> *spanSet.spine[0] -> *spanSetBlock.spans[0]
// ... ...
// ... *spanSetBlock.spans[511]
// .spine[255]
// ...
// .spine[N]
// .partial[1]
// .full[0]
// .full[1]
// ...
// mcentral[67]
// ...
// mcentral[135]
//

注意

由于spanclass = sizeclass << 1 + noscan,所以,奇数索引纪录scalar类型,偶数纪录索引类型

mheap

负责向操作系统申请内存进行分配/管理、纪录内存分配信息等。全局唯一,访问需要加锁或者STW。其中

  1. 页通过pages-页分配器分配、管理
  2. mspan通过spanalloc分配器创建/回收,关联页后纪录到allspans,mspan空间用完/不足会放到mcentral管理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
type mheap struct {
_ sys.NotInHeap

lock mutex // 锁

// 页分配器,用于查找连续n页的起始地址
pages pageAlloc

sweepgen uint32 // 版本计数器

allspans []*mspan // 通过spanalloc分配的mspan都纪录到这里

// GC清扫时使用
pagesInUse atomic.Uintptr // heap内存页使用量(mSpanInUse)
pagesSwept atomic.Uint64 // 已完成清扫的页数量
pagesSweptBasis atomic.Uint64 // pagesSwept快照
sweepHeapLiveBasis uint64 // heapLive快照(标记终止阶段纪录用于清扫阶段)
// =剩余待清扫页数量/距离GC触发的剩余堆大小
// 如果sweepPagesPerByte=0.01,那么每分配100字节,就需要清扫1个页
// 分配对象时同步清扫,以确保GC触发前清扫任务能完成
sweepPagesPerByte float64 // GC触发前,每分配1字节需要清扫的页数

// 内存回收时用。先扣额度,额度用完则扫描heapArena
// 额度用完时使用,索引从0开始,扫描完所有heapArena设置为1 << 63
reclaimIndex atomic.Uint64 // heapArena索引
// 释放完整的mspan或释放过多页时才会累计,避免scavenger过度回收
reclaimCredit atomic.Uintptr // 回收额度/回收积分

_ cpu.CacheLinePad

// bitmap,二维数组,linux下结构为[1]*[4194304]*heapArena,其中
// 1维只有一个元素,二维有4M个指针,每个heapArena负责管理64MB内存,共管理256TB内存
// 因为基地址是0xffff800000000000,实际管理内存为128TB
arenas [1 << arenaL1Bits]*[1 << arenaL2Bits]*heapArena

arenasHugePages bool // 是否启用huge page,linux下可用
heapArenaAlloc linearAlloc // 线性分配器,32位平台用
arenaHints *arenaHint // 链表,每个hint纪录一段heapArena占用的地址
arena linearAlloc // 线性分配器,32位平台用
allArenas []arenaIdx // 纪录arenaIdx
sweepArenas []arenaIdx // allArenas快照,GC清扫时用
markArenas []arenaIdx // allArenas快照,GC标记时用
curArena struct { // 当前heapArena的基地址和下一个可分配的内存地址
base, end uintptr
}

// 分配<32KB内存时会先尝试清扫重用mcentral的mspan
central [numSpanClasses]struct { // 共136个mcentral
mcentral mcentral
pad [(cpu.CacheLinePadSize - unsafe.Sizeof(mcentral{})%cpu.CacheLinePadSize) % cpu.CacheLinePadSize]byte
}

// 下面都是fixalloc类型的内存分配器,用于创建各类header,固定16KB,用完后重新从系统申请
spanalloc fixalloc // span*
cachealloc fixalloc // mcache*
specialfinalizeralloc fixalloc // specialfinalizer*
specialCleanupAlloc fixalloc // specialcleanup*
specialprofilealloc fixalloc // specialprofile*
specialReachableAlloc fixalloc // specialReachable
specialPinCounterAlloc fixalloc // specialPinCounter
specialWeakHandleAlloc fixalloc // specialWeakHandle
speciallock mutex //
arenaHintAlloc fixalloc // arenaHints

// arena包用,用于手动分配内存
userArena struct {
arenaHints *arenaHint // 链表,初始化时创建128个arenaHint
quarantineList mSpanList //
readyList mSpanList //
}

cleanupID uint64 // 计数器

unused *specialfinalizer
}

// 预规划地址,目的是尽量让地址连续
type arenaHint struct {
_ sys.NotInHeap

// 这里的addr是预设的,跟系统申请内存时尝试用sysMap直接映射,失败则回退到sysAlloc申请
addr uintptr // arena的开始虚拟地址
down bool // 分配内存时地址增长的方向,一般为false,即向上扩展
next *arenaHint // 指针,指向下一个arenaHint
}

// bitmap,管理8K个页,每个页8192B,共64MB内存
type heapArena struct {
_ sys.NotInHeap

// 精确纪录
// bitmap,共8192个mspan指针,每一个元素表示该页被哪个mspan使用,如
//
// | 0 | 1 | ... | 8191 | -> 通过地址addr找到页的索引idx
// | page7 | page8 | ... | page8198 |
// | *mspan3 | *mspan3 | ... | *mspan9 | -> 页7、页8被mspan3使用
spans [pagesPerArena]*mspan

// 粗略纪录,pageInUse纪录的颗粒度比较大,如果有n个页,只会纪录起始页
pageInUse [pagesPerArena / 8]uint8 // 用于标记mSpanInUse状态的mspan,共1KB
pageMarks [pagesPerArena / 8]uint8 // 同上,GC用
pageSpecials [pagesPerArena / 8]uint8 //

checkmarks *checkmarksMap // debug.gccheckmark不为0时使用(一般用不到)
zeroedBase uintptr // 类似base,指向已经清0的区域,如果内存重用,分配的地址会比zeroedBase小,表示需要清0
}

hint作用

hint-预规划地址,它的作用是通过sysMap直接向操作系统申请在指定地址处映射内存,因为使用sysAlloc让操作系统选择地址空间会导致地址不连续、碎片化等。如果失败最后会回退到sysAlloc

mheap示例

1
2
3
4
5
6
7
// mheap.arenas[0] -> *heapArena[0](64MB)
// ...
// *heapArena[4194303]
//
// .central -> mcentral[0]
// ...
// mcentral[135]

可用内存空间

  1. 从arenas结构看,在linux系统下,mheap可以管理1419430464MB=256TB内存,但实际上,go选择内存地址的中间-0xffff800000000000作为基地址,计算最大地址0x007ffffffff000减去基地址,得出实际可管理内存为128TB
  2. arena包用于手动管理内存,从预分配地址看,实际可管理内存也为128TB,当然,它选择的是另一个地址空间,不跟heap共用

分配器

fixalloc

mheap大多数分配器是fixalloc类型,比如mspan就使用了fixalloc,每次用完就从系统申请16KB内存。分配内存优先从空闲对象链表list获取,没有才从chunk分配内存

1
2
3
4
5
6
7
8
9
10
11
12
type fixalloc struct {
size uintptr // 元素大小,如mspan数据类型的大小为160B
first func(arg, p unsafe.Pointer) // 一般为nil,除了mspan需要纪录每个页对应的mspan
arg unsafe.Pointer // 函数参数,比如mspan指针
list *mlink // 空闲对象列表,比如mspan释放时会放到这里,后续重用
chunk uintptr // 内存指针,指向未使用的内存的起始位置,也可以认为是offset
nchunk uint32 // 剩余字节数,初始值为nalloc
nalloc uint32 // 一般是16KB,但会优化移除尾部不用的部份,按8的倍数对齐
inuse uintptr // 已使用字节数
stat *sysMemStat // 内存统计用
zero bool // 默认都需要清0
}

pageAlloc

页分配器,本身属于bitmap,用于快速查找连续n页的起始地址。实现比较复杂,是非常重要的数据结构。其中

  1. chunks为二维数组,总数据量与arena相同,一次性可管理512个页,即4MB内存
  2. summary是chunks的索引,分5层,最后一层数量与chunks总数相同,每上一层数量缩减8倍,管理的页数增长8倍,可快速寻找连续n页内存的起始地址

注意

pageAlloc对bitmap的处理与其他分配器正相反,1为已使用,0为未使用,刚开始看代码很容易混淆

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
type pageAlloc struct {
// summary => [0][2^14]pallocSum => 第1层每8个sum汇总成1个sum => 每个sum管理 2M个页共4GB内存
// [1][2^17]pallocSum => 第2层每8个sum汇总成1个sum => 每个sum管理256K个页共2GB内存
// [2][2^20]pallocSum => 第3层每8个sum汇总成1个sum => 每个sum管理 32K个页共256MB内存
// [3][2^23]pallocSum => 第4层每8个sum汇总成1个sum => 每个sum管理 4K个页共32MB内存
// [4][2^26]pallocSum => 代表8192*8192个chunk => 每个sum管理 512个页共4MB内存
//
// pallocSum => | 1bit | 21bit | 21bit | 21bit |
// | allmax | end | max | start |
//
summary [summaryLevels][]pallocSum

// chunks -> [0]
// ...
// [8191] -> [0] -> pallocBits(512 bit) + scavenged(512 bit)
// | ...
// v [8191] -> 一个单元代表512*8KB=4MB内存
// 一个单元代表8192*4MB=32GB
//
// scavenged初始化时设置为全1,pallocBits初始化时设置为全0的bitmap
// 可标记n=8192*8192*64*8个page指针,共n*8192=256TB内存,与mheap管理的arena的内存管理量相同
chunks [1 << pallocChunksL1Bits]*[1 << pallocChunksL2Bits]pallocData

searchAddr offAddr // 搜索地址,避免每次从基地址开始搜索

// 这里的索引计算刚开始看会很困惑,因为它是把2维数组当1维数组用了,索引范围是[0,8192*8192-1]
// start会纪录全局最小的索引,而end则纪录全局最大的索引
start, end chunkIdx // 起始、终止索引

inUse addrRanges // 扩容时纪录内存使用情况

scav struct { //
index scavengeIndex //
releasedBg atomic.Uintptr //
releasedEager atomic.Uintptr //
}

mheapLock *mutex // mheap.lock字段
sysStat *sysMemStat //
summaryMappedReady uintptr // 已映射且可用的内存量,测试用
chunkHugePages bool // 开启huge page时,设置为true

test bool // 测试,忽略
}

type addrRanges struct {
ranges []addrRange // 已分配的地址段,每个元素纪录base、limit,插入时会进行合并
totalBytes uintptr // 总分配字节数
sysStat *sysMemStat //
}

// | 1bit | 21bit | 21bit | 21bit |
// | allmax | end | max | start |
type pallocSum uint64

// bitmap,负责管理64*8=512个页 => 4MB内存
type pallocData struct {
pallocBits // 分配标记,64字节
scavenged pageBits // 回收标记,64字节
}

type pageBits [pallocChunkPages / 64]uint64 // 512字节,共8个uint64

mallocgc

slice、map、string申请内存时是通过mallocgc来分配的,其他如newobject、newarray、reflect.unsafe_New底层实际也是在调用mallocgc

mallocg内部对申请的内存大小size、对象的类型type判断,分别调用不同的内存分配器分配内存,具体如下

条件1 条件2 条件3 内存分配器
<=32760B nil或对象不包含指针 <16B mallocgcTiny
>=16B mallocgcSmallNoscan
对象包含指针 <=512B mallocgcSmallScanNoHeader
>512B mallocgcSmallScanHeader
>32760B mallocgcLarge
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
// 根据请求的内存大小分配内存、返回地址
func mallocgc(size uintptr, typ *_type, needzero bool) unsafe.Pointer {
// size为0
if size == 0 {
// 一个全局变量的指针
return unsafe.Pointer(&zerobase)
}

// 空函数,忽略(staticlockranking默认为false)
lockRankMayQueueFinalizer()

// GC已启动(gcStart时为true)
if gcBlackenEnabled != 0 {
// 降低g的Assist额度,如果额度用光了,则g需要协助GC标记(g会被挂起)
deductAssistCredit(size)
}

// 新申请的内存区域起始地址
var x unsafe.Pointer
// 对象大小,以mspan的为准
var elemsize uintptr

// 总申请字节数 <= 32KB(32768-8)
if size <= maxSmallSize-mallocHeaderSize {
// 没有类型信息 or 对象不包含指针(由scalar组成)
if typ == nil || !typ.Pointers() {
if size < maxTinySize {
// < 16B
// 从mcache.tiny分配
x, elemsize = mallocgcTiny(size, typ, needzero)
} else {
// >= 16B
// 计算spanclass获取span,分配size大小的内存返回
x, elemsize = mallocgcSmallNoscan(size, typ, needzero)
}

// 下面的是有类型信息 and 包含指针的情况
} else if heapBitsInSpan(size) {
// <=512B
// 直接存储对象、设置heapBits
x, elemsize = mallocgcSmallScanNoHeader(size, typ, needzero)
} else {
// >512B,<=32KB
// 对象大小增加8字节用于存储数据类型、设置heapBits
x, elemsize = mallocgcSmallScanHeader(size, typ, needzero)
}
} else {
// > 32KB
// 一个对象占用一个mspan
x, elemsize = mallocgcLarge(size, typ, needzero)
}

// GC已启动(gcStart时为true) and elemsize不为0
if gcBlackenEnabled != 0 && elemsize != 0 {
// 重复,避免当前g是调度的g0
if assistG := getg().m.curg; assistG != nil {
// 额度-=剩余量
// 先前只是按传入的size减少额度,到这里,size已elemsize为准,需要把多余的量也减去
assistG.gcAssistBytes -= int64(elemsize - size)
}
}

return x
}

mallocgcTiny

前提条件:size<=32KB,对象为nil或对象不包含指针,size<16B。tiny区域只有16字节,用于合并多个微小对象的内存分配,大概逻辑如下

  1. 访问mcache
    • 有p则获取p.mcache,没有p则获取mcache0
  2. tiny区域调整、计算
    • tiny区域的offset偏移量跟内存分配量size对齐
  3. 空间足够(offset+size<=61)
    • 更新偏移量、计数器,返回内存区域起始地址
  4. 空间不足
    • 申请新的mspan
      • 从mcache.alloc[tinySpanClass]获取mspan
    • 快速分配
      • 通过64位的allocCache快速判断并分配对象
    • 慢速分配
      • 从allocBits获取64位数据,重新填充allocCache并重新判断分配对象
      • 如果mspan已满/空间不足,则从mcentral获取新的mspan替换原mspan后重试
  5. 收尾
    • 分配的16字节内存区域清0
    • 如果size比offse小或tiny区域未初始化,替换tiny区域、更新信息
    • 如果size比offset大,如offset=8,size=12,直接返回整个16字节内存块,不更新tiny区域
    • 写屏障、profiling、GC处理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
// 从tiny区域分配
func mallocgcTiny(size uintptr, typ *_type, needzero bool) (unsafe.Pointer, uintptr) {
// 防止抢占
mp := acquirem()

// 防止被GC抢占
mp.mallocing = 1

// 获取p.mcache,没有p则获取mcache0
c := getMCache(mp)

// 偏移量(指向下一个可用区域)
off := c.tinyoffset
// 调整偏移量,按指定倍数向上取整
if size&7 == 0 { // 低3位为0
// 按8的倍数向上取整
off = alignUp(off, 8)
} else if goarch.PtrSize == 4 && size == 12 { // 32位系统 and 12B
// 按8的倍数向上取整
off = alignUp(off, 8)
} else if size&3 == 0 { // 低2位为0
// 按4的倍数向上取整
off = alignUp(off, 4)
} else if size&1 == 0 { // 低1位为0
// 按2的倍数向上取整
off = alignUp(off, 2)
}

// 有足够的空间容纳元素 and tiny区域已初始化
if off+size <= maxTinySize && c.tiny != 0 {
// 获取内存地址
x := unsafe.Pointer(c.tiny + off)
// 更新偏移量
c.tinyoffset = off + size
// 计数器tinyAllocs+=1
c.tinyAllocs++
// 重置
mp.mallocing = 0
releasem(mp)
return x, 0
}

// 空间不足(最大16字节) or tiny区域未初始化

// 是否需要触发GC清扫(分配了新的mspan)
checkGCTrigger := false
// mspan,索引tinySpanClass=5(sizeclass=2,noscan=true)
span := c.alloc[tinySpanClass]
// 通过64位的allocCache快速判断并分配对象
v := nextFreeFast(span)
// 64个对象已经被分配完了,进入slow path
if v == 0 {
// mspan获取一个可用对象,如果mspan已满,则从mcentral获取新的mspan替换原mspan后重试
v, span, checkGCTrigger = c.nextFree(tinySpanClass)
}

x := unsafe.Pointer(v)
// 内存区域清0(共16个字节)
(*[2]uint64)(x)[0] = 0
(*[2]uint64)(x)[1] = 0

// raceenabled默认为false,因此固定为true and (size比已offset小 or tiny未初始化)
if !raceenabled && (size < c.tinyoffset || c.tiny == 0) {
c.tiny = uintptr(x)
c.tinyoffset = size
}

// 如果size比offset大,如offset=8,size=12,直接返回整个x内存块,不更新tiny区域

// 汇编,linux+amd64下为空函数
publicationBarrier()

// 同步freeindex
span.freeIndexForScan = span.freeindex

// 写屏障已开启
if writeBarrier.enabled {
// 新分配对象标记为黑色,类似greyobject
gcmarknewobject(span, uintptr(x))
}

// nextSample初始值为随机数:[0,MemProfileRate)
c.nextSample -= int64(span.elemsize)
// 负数立即采样 or MemProfileRate有改动
if c.nextSample < 0 || MemProfileRate != c.memProfRate {
// 重置相关字段,记录内存分配事件,添加special防止GC回收
profilealloc(mp, x, span.elemsize)
}
// 重置
mp.mallocing = 0
releasem(mp)

// 如果需要触发GC清扫
if checkGCTrigger {
// 内存达到阈值,执行GC
if t := (gcTrigger{kind: gcTriggerHeap}); t.test() {
gcStart(t)
}
}

return x, span.elemsize
}

mallocgcSmallNoscan

前提条件:size<=32KB,对象为nil或对象不包含指针,size>=16B。大概逻辑如下

  1. 访问mcache
    • 有p则获取p.mcache,没有p则获取mcache0
  2. 获取mspan
    • 通过组合sizeclass和noscan变量计算出spanclass
      • 如果size<=1016(1KB-8),sizeclass范围是[0,32]
      • 如果size>1016,sizeclass范围是[32,67]
        从mcache.alloc[sizeclass]获取mspan
  3. 分配内存
    • 快速分配
      • 通过64位的allocCache快速判断并分配对象
    • 慢速分配
      • 从allocBits获取64位数据,重新填充allocCache并重新判断分配对象
      • 如果mspan已满/空间不足,则从mcentral获取新的mspan替换原mspan后重试
  4. 收尾
    • 内存区域清0
    • 写屏障、profiling、GC处理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
// 计算spanclass获取span,分配size大小的内存返回
func mallocgcSmallNoscan(size uintptr, typ *_type, needzero bool) (unsafe.Pointer, uintptr) {
mp := acquirem()

// 防止被GC抢占
mp.mallocing = 1

// 是否需要触发GC清扫(分配了新的mspan)
checkGCTrigger := false

// 获取p.mcache,没有p则获取mcache0
c := getMCache(mp)

var sizeclass uint8
if size <= smallSizeMax-8 {
// <=1016
// =size_to_class8[ceil(size/8)](sizeclass范围是[0,32],闭区间)
sizeclass = size_to_class8[divRoundUp(size, smallSizeDiv)]
} else {
// >1016
// =size_to_class128[ceil((size-1024)/128)](sizeclass范围是[32,67],闭区间)
sizeclass = size_to_class128[divRoundUp(size-smallSizeMax, largeSizeDiv)]
}
// 向上取整,如原size=20,经过计算sizeclass=3,新size=24
size = uintptr(class_to_size[sizeclass])
// 组合sizeclass和noscan成spanclass
spc := makeSpanClass(sizeclass, true)
// 找到mspan
span := c.alloc[spc]

// 通过64位的allocCache快速判断并分配对象
v := nextFreeFast(span)
// 64个对象已经被分配完了,进入slow path
if v == 0 {
// mspan获取一个可用对象,如果mspan已满,则从mcentral获取新的mspan替换原mspan后重试
v, span, checkGCTrigger = c.nextFree(spc)
}

x := unsafe.Pointer(v)

// 需要清0 and span分配前需要清0
if needzero && span.needzero != 0 {
// 内存区域清0
memclrNoHeapPointers(x, size)
}

// 汇编,linux+amd64下为空函数
publicationBarrier()

// 同步freeindex
span.freeIndexForScan = span.freeindex

// 写屏障已开启
if writeBarrier.enabled {
// 新分配对象标记为黑色,类似greyobject
gcmarknewobject(span, uintptr(x))
}

// nextSample初始值为随机数:[0,MemProfileRate)
c.nextSample -= int64(size)
// 负数立即采样 or MemProfileRate有改动
if c.nextSample < 0 || MemProfileRate != c.memProfRate {
// 重置相关字段,记录内存分配事件,添加special防止GC回收
profilealloc(mp, x, size)
}
// 重置
mp.mallocing = 0
releasem(mp)

// 如果需要触发GC清扫
if checkGCTrigger {
// 内存达到阈值,执行GC
if t := (gcTrigger{kind: gcTriggerHeap}); t.test() {
gcStart(t)
}
}
return x, size
}

mallocgcSmallScanNoHeader

前提条件:size<=32KB,对象包含指针,size<=512B。大概逻辑如下

  1. 访问mcache
    • 有p则获取p.mcache,没有p则获取mcache0
  2. 获取mspan
    • 通过组合sizeclass和noscan变量计算出spanclass,最终sizeclass范围是[0,32]
    • 从mcache.alloc[sizeclass]获取mspan
  3. 分配内存
    • 快速分配
      • 通过64位的allocCache快速判断并分配对象
    • 慢速分配
      • 从allocBits获取64位数据,重新填充allocCache并重新判断分配对象
      • 如果mspan已满/空间不足,则从mcentral获取新的mspan替换原mspan后重试
  4. 收尾
    • 根据sizeclass调整scanAlloc、size
    • 写屏障、profiling、GC处理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
func mallocgcSmallScanNoHeader(size uintptr, typ *_type, needzero bool) (unsafe.Pointer, uintptr) {
mp := acquirem()

// 防止被GC抢占
mp.mallocing = 1

// 是否需要触发GC清扫(分配了新的mspan)
checkGCTrigger := false

// 获取p.mcache,没有p则获取mcache0
c := getMCache(mp)

// =size_to_class8[ceil(size/8)](sizeclass范围是[0,32],闭区间)
sizeclass := size_to_class8[divRoundUp(size, smallSizeDiv)]
// 组合sizeclass和noscan成spanclass
spc := makeSpanClass(sizeclass, false)
// 找到mspan
span := c.alloc[spc]

// 通过64位的allocCache快速判断并分配对象
v := nextFreeFast(span)
// 64个对象已经被分配完了,进入slow path
if v == 0 {
// mspan获取一个可用对象,如果mspan已满,则从mcentral获取新的mspan替换原mspan后重试
v, span, checkGCTrigger = c.nextFree(spc)
}

x := unsafe.Pointer(v)

// 需要清0 and span分配前需要清0
if needzero && span.needzero != 0 {
// 内存区域清0
memclrNoHeapPointers(x, size)
}

// 64位操作系统 and sizeclass为1(>8B)
if goarch.PtrSize == 8 && sizeclass == 1 {
// mcentral在grow时已调用initHeapBits设置
c.scanAlloc += 8
} else {
// 32位系统 or sizeclass不为1(>8B)
// bitmap纪录指针位置,返回需要扫描的字节数
c.scanAlloc += heapSetTypeNoHeader(uintptr(x), size, typ, span)
}
// 向上取整,如原size=20,经过计算sizeclass=3,新size=24
size = uintptr(class_to_size[sizeclass])

// 汇编,linux+amd64下为空函数
publicationBarrier()

// 同步freeindex
span.freeIndexForScan = span.freeindex

// 写屏障已开启
if writeBarrier.enabled {
// 新分配对象标记为黑色,类似greyobject
gcmarknewobject(span, uintptr(x))
}

// nextSample初始值为随机数:[0,MemProfileRate)
c.nextSample -= int64(size)
// 负数立即采样 or MemProfileRate有改动
if c.nextSample < 0 || MemProfileRate != c.memProfRate {
// 重置相关字段,记录内存分配事件,添加special防止GC回收
profilealloc(mp, x, size)
}
// 重置
mp.mallocing = 0
releasem(mp)

// 如果需要触发GC清扫
if checkGCTrigger {
// 内存达到阈值,执行GC
if t := (gcTrigger{kind: gcTriggerHeap}); t.test() {
gcStart(t)
}
}
return x, size
}

mallocgcSmallScanHeader

前提条件:size<=32KB,对象包含指针,size>512B。大概逻辑如下

  1. 访问mcache
    • 有p则获取p.mcache,没有p则获取mcache0
  2. 获取mspan
    • size+=8(多分配8字节存储type)
    • 通过组合sizeclass和noscan变量计算出spanclass
      • 如果size<=1016(1KB-8),sizeclass范围是[0,32]
      • 如果size>1016,sizeclass范围是[32,67]
    • 从mcache.alloc[sizeclass]获取mspan
  3. 分配内存
    • 快速分配
      • 通过64位的allocCache快速判断并分配对象
    • 慢速分配
      • 从allocBits获取64位数据,重新填充allocCache并重新判断分配对象
      • 如果mspan已满/空间不足,则从mcentral获取新的mspan替换原mspan后重试
  4. 收尾
    • 内存区域清0
    • 存储type到内存区域头8个字节,调整内存区域指针、scanAlloc、size
    • 写屏障、profiling、GC处理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
func mallocgcSmallScanHeader(size uintptr, typ *_type, needzero bool) (unsafe.Pointer, uintptr) {
mp := acquirem()

// 防止被GC抢占
mp.mallocing = 1

// 是否需要触发GC清扫(分配了新的mspan)
checkGCTrigger := false

// 获取p.mcache,没有p则获取mcache0
c := getMCache(mp)

// 多申请8B用作header
size += mallocHeaderSize

var sizeclass uint8
if size <= smallSizeMax-8 {
// <=1016
// =size_to_class8[ceil(size/8)](sizeclass范围是[0,32],闭区间)
sizeclass = size_to_class8[divRoundUp(size, smallSizeDiv)]
} else {
// >1016
// =size_to_class128[ceil((size-1024)/128)](sizeclass范围是[32,67],闭区间)
sizeclass = size_to_class128[divRoundUp(size-smallSizeMax, largeSizeDiv)]
}
// 向上取整,如原size=20,经过计算sizeclass=3,新size=24
size = uintptr(class_to_size[sizeclass])
// 组合sizeclass和noscan成spanclass
spc := makeSpanClass(sizeclass, false)
// 找到mspan
span := c.alloc[spc]

// 通过64位的allocCache快速判断并分配对象
v := nextFreeFast(span)
// 64个对象已经被分配完了,进入slow path
if v == 0 {
// mspan获取一个可用对象,如果mspan已满,则从mcentral获取新的mspan替换原mspan后重试
v, span, checkGCTrigger = c.nextFree(spc)
}

x := unsafe.Pointer(v)

// 需要清0 and span分配前需要清0
if needzero && span.needzero != 0 {
// 内存区域清0
memclrNoHeapPointers(x, size)
}

// 开始的8B用作header
header := (**_type)(x)
// 往后移动8B才是实际存储
x = add(x, mallocHeaderSize)
// 把typ存储到header,返回span.elemsize
c.scanAlloc += heapSetTypeSmallHeader(uintptr(x), size-mallocHeaderSize, typ, header, span)

// 汇编,linux+amd64下为空函数
publicationBarrier()

// 同步freeindex
span.freeIndexForScan = span.freeindex

// 写屏障已开启
if writeBarrier.enabled {
// 新分配对象标记为黑色,类似greyobject
gcmarknewobject(span, uintptr(x))
}

// nextSample初始值为随机数:[0,MemProfileRate)
c.nextSample -= int64(size)
// 负数立即采样 or MemProfileRate有改动
if c.nextSample < 0 || MemProfileRate != c.memProfRate {
// 重置相关字段,记录内存分配事件,添加special防止GC回收
profilealloc(mp, x, size)
}
// 重置
mp.mallocing = 0
releasem(mp)

// 如果需要触发GC清扫
if checkGCTrigger {
// 内存达到阈值,执行GC
if t := (gcTrigger{kind: gcTriggerHeap}); t.test() {
gcStart(t)
}
}
return x, size
}

mallocgcLarge

前提条件:size>32KB。大概逻辑如下

  1. 访问mcache
    • 有p则获取p.mcache,没有p则获取mcache0
  2. 创建mspan
    • 通过mheap分配size大小的mspan(size会按一定的倍数向上取整),更新mspan信息
    • 需要协助GC清扫
  3. 收尾
    • 内存区域清0,如果是scan则纪录对象类型到largeType
    • 写屏障、profiling、GC处理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
func mallocgcLarge(size uintptr, typ *_type, needzero bool) (unsafe.Pointer, uintptr) {
mp := acquirem()

// 防止被GC抢占
mp.mallocing = 1

// 获取p.mcache,没有p则获取mcache0
c := getMCache(mp)

// func (c *mcache) allocLarge(size uintptr, noscan bool) *mspan
// 协助sweeper清扫,从mheap分配内存、更新索引/gc等信息,mspan放到fullSwept
span := c.allocLarge(size, typ == nil || !typ.Pointers())
// 下一个可用对象索引
span.freeindex = 1
// 已分配对象数
span.allocCount = 1
// 先设置为nil,避免GC扫描,特别是noscan类型
span.largeType = nil
// 调整为mspan的对象大小,对齐后
size = span.elemsize

// mspan的起始地址
x := unsafe.Pointer(span.base())

// 汇编,linux+amd64下为空函数
publicationBarrier()

// 同步freeindex
span.freeIndexForScan = span.freeindex

// 写屏障已开启
if writeBarrier.enabled {
// 新分配对象标记为黑色,类似greyobject
gcmarknewobject(span, uintptr(x))
}

// nextSample初始值为随机数:[0,MemProfileRate)
c.nextSample -= int64(size)
// 负数立即采样 or MemProfileRate有改动
if c.nextSample < 0 || MemProfileRate != c.memProfRate {
// 重置相关字段,记录内存分配事件,添加special防止GC回收
profilealloc(mp, x, size)
}

// 重置
mp.mallocing = 0
releasem(mp)

// 内存达到阈值,执行GC
if t := (gcTrigger{kind: gcTriggerHeap}); t.test() {
gcStart(t)
}

// 如果是scan类型 or (需要清0 and span分配前需要清0)
if noscan := typ == nil || !typ.Pointers(); !noscan || (needzero && span.needzero != 0) {
// 内存区域批量清0,发生抢占则挂起
memclrNoHeapPointersChunked(size, x)

// 防止被GC抢占
mp := acquirem()
// 如果是scan类型
if !noscan {
// 获取p.mcache,没有p则获取mcache0
// 把typ存储到span.largeType,返回span.elemsize
getMCache(mp).scanAlloc += heapSetTypeLarge(uintptr(x), size, typ, span)
}
// 汇编,linux+amd64下为空函数
publicationBarrier()
releasem(mp)
}
return x, size
}

相关依赖函数

malloc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
// malloc初始化,schedinit-调度器初始化时调用
func mallocinit() {
// 1. guard,各种检查

// class_to_size[2] != 16
if class_to_size[_TinySizeClass] != _TinySize {
throw("bad TinySizeClass")
}

// heapArenaBitmapWords非2的倍数
if heapArenaBitmapWords&(heapArenaBitmapWords-1) != 0 {
throw("heapArenaBitmapWords not a power of 2")
}

// physPageSize没有初始化(该变量由osinit初始化)
if physPageSize == 0 {
throw("failed to get system page size")
}
// physPageSize大于524288
if physPageSize > maxPhysPageSize {
print("system page size (", physPageSize, ") is larger than maximum page size (", maxPhysPageSize, ")\n")
throw("bad system page size")
}
// physPageSize小于4096
if physPageSize < minPhysPageSize {
print("system page size (", physPageSize, ") is smaller than minimum page size (", minPhysPageSize, ")\n")
throw("bad system page size")
}
// physPageSize大小非2的倍数
if physPageSize&(physPageSize-1) != 0 {
print("system page size (", physPageSize, ") must be a power of 2\n")
throw("bad system page size")
}
// physHugePageSize大小非2的倍数(由osinit初始化,只有linux才有)
if physHugePageSize&(physHugePageSize-1) != 0 {
print("system huge page size (", physHugePageSize, ") must be a power of 2\n")
throw("bad system huge page size")
}
// 大于4194304
if physHugePageSize > maxPhysHugePageSize {
// 改为不支持
physHugePageSize = 0
}
// physHugePageSize有正常数值
if physHugePageSize != 0 {
// 计算physHugePageShift,使2^physHugePageShift==physHugePageSize
for 1<<physHugePageShift != physHugePageSize {
physHugePageShift++
}
}
// 8192%512
if pagesPerArena%pagesPerSpanRoot != 0 {
print("pagesPerArena (", pagesPerArena, ") is not divisible by pagesPerSpanRoot (", pagesPerSpanRoot, ")\n")
throw("bad pagesPerSpanRoot")
}
// 8192%512
if pagesPerArena%pagesPerReclaimerChunk != 0 {
print("pagesPerArena (", pagesPerArena, ") is not divisible by pagesPerReclaimerChunk (", pagesPerReclaimerChunk, ")\n")
throw("bad pagesPerReclaimerChunk")
}

// 下面两个最终的结果都为true
minSizeForMallocHeaderIsSizeClass := false
sizeClassesUpToMinSizeForMallocHeaderAreOnePage := true
// 68个元素
for i := 0; i < len(class_to_size); i++ {
if class_to_allocnpages[i] > 1 {
sizeClassesUpToMinSizeForMallocHeaderAreOnePage = false
}
// 512 == class_to_size[i]
if minSizeForMallocHeader == uintptr(class_to_size[i]) {
minSizeForMallocHeaderIsSizeClass = true
break
}
}
if !minSizeForMallocHeaderIsSizeClass {
throw("min size of malloc header is not a size class boundary")
}
if !sizeClassesUpToMinSizeForMallocHeaderAreOnePage {
throw("expected all size classes up to min size for malloc header to fit in one-page spans")
}

// 512/8 > 8*8 => false
if minSizeForMallocHeader/goarch.PtrSize > 8*goarch.PtrSize {
throw("max pointer/scan bitmap size for headerless objects is too large")
}

// 10 > 19 => false
if minTagBits > taggedPointerBits {
throw("taggedPointerBits too small")
}

// 2. 执行相关初始化

// mheap初始化(感觉没什么好说的)
mheap_.init()
// mcache0从cachealloc分配器申请内存初始化
mcache0 = allocmcache()
// 锁初始化
lockInit(&gcBitsArenas.lock, lockRankGcBitsArenas)
lockInit(&profInsertLock, lockRankProfInsert)
lockInit(&profBlockLock, lockRankProfBlock)
lockInit(&profMemActiveLock, lockRankProfMemActive)
for i := range profMemFutureLock {
lockInit(&profMemFutureLock[i], lockRankProfMemFuture)
}
// 全局内存分配器
lockInit(&globalAlloc.mutex, lockRankGlobalAlloc)

// 下面是userArena的初始化,用以Arena包,让用户自己手动管理内存,可以先忽略
if isSbrkPlatform {
// wasm(isSbrkPlatform默认为false)
} else if goarch.PtrSize == 8 {
// 64位系统

// 生成hint-预规划地址,后期尽量调用sysMap直接映射,而不是sysAlloc让OS选择,减少碎片化
// 为了防止冲突和区分用途,选择0x00c0作为该内存段的标记

// i=127,共128个,每个arena间隔1<<40(即1TB),共128TB
for i := 0x7f; i >= 0; i-- {
// 第一步,计算p
var p uintptr
switch {
case raceenabled: // raceenabled默认为false,忽略
// TSAN要求heap必须在[0x00c000000000, 0x00e000000000)范围内
p = uintptr(i)<<32 | uintptrMask&(0x00c0<<32)
if p >= uintptrMask&0x00e000000000 {
continue
}
case GOARCH == "arm64" && GOOS == "ios": // 忽略
p = uintptr(i)<<40 | uintptrMask&(0x0013<<28)
case GOARCH == "arm64": // 忽略
p = uintptr(i)<<40 | uintptrMask&(0x0040<<32)
case GOOS == "aix": // 忽略
if i == 0 {
// 防止跟mmap冲突(mmap使用0x0A00000000000000开始的地址)
continue
}
p = uintptr(i)<<40 | uintptrMask&(0xa0<<52)
default: // 默认
// p = (i*2^40)|(2^64-1)&(0x00c0<<32)
// i最大值为127,p总共占用47个位,&优先级比|高
// 如果i=127(0x7f),p的结果是0x007FC00000000000(7f和c被保留)
p = uintptr(i)<<40 | uintptrMask&(0x00c0<<32)
}

// 第二步,arenaHint链表
// 下面判断中,127个hint中的前64个(即一半)hints由mheap直接管理,剩余的放入userArena
hintList := &mheap_.arenaHints
// raceenabled默认为false
if (!raceenabled && i > 0x3f) || (raceenabled && i > 0x5f) {
// 如果i > 0x3f,走这里
hintList = &mheap_.userArena.arenaHints
}

// 从arenaHintAlloc里获取24字节作为arenaHint(内存不够用一次性申请16KB)
hint := (*arenaHint)(mheap_.arenaHintAlloc.alloc())
// 纪录地址p
hint.addr = p

// 链成一个链表
// hint0->hint1->...->hint127
// curr.next = *prev; *prev = curr
hint.next, *hintList = *hintList, hint
}
} else {
// 32位系统,直接忽略
// ...
}

// 默认2^64-1,可以通过GOMEMLIMIT环境变量修改
gcController.memoryLimit.Store(maxInt64)
}

mheap

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
// mheap初始化
func (h *mheap) init() {
// 锁初始化
lockInit(&h.lock, lockRankMheap)
lockInit(&h.speciallock, lockRankMheapSpecial)

// 下面全是初始化fixalloc结构体(allock时若数据量不足,则一次性申请16KB内存)

// mspan,每个单元160字节
// 每次alloc执行recordspan函数,其将分配的mspan纪录到allspans
h.spanalloc.init(unsafe.Sizeof(mspan{}), recordspan, unsafe.Pointer(h), &memstats.mspan_sys)
// mcache,每个单元1208字节
h.cachealloc.init(unsafe.Sizeof(mcache{}), nil, nil, &memstats.mcache_sys)
// specialfinalizer,每个单元56字节
h.specialfinalizeralloc.init(unsafe.Sizeof(specialfinalizer{}), nil, nil, &memstats.other_sys)
// specialCleanup,每个单元40字节
h.specialCleanupAlloc.init(unsafe.Sizeof(specialCleanup{}), nil, nil, &memstats.other_sys)
// specialprofile,每个单元32字节
h.specialprofilealloc.init(unsafe.Sizeof(specialprofile{}), nil, nil, &memstats.other_sys)
// specialReachable,每个单元32字节
h.specialReachableAlloc.init(unsafe.Sizeof(specialReachable{}), nil, nil, &memstats.other_sys)
// specialPinCounter,每个单元32字节
h.specialPinCounterAlloc.init(unsafe.Sizeof(specialPinCounter{}), nil, nil, &memstats.other_sys)
// specialWeakHandle,每个单元32字节
h.specialWeakHandleAlloc.init(unsafe.Sizeof(specialWeakHandle{}), nil, nil, &memstats.gcMiscSys)
// arenaHint,每个单元24字节
h.arenaHintAlloc.init(unsafe.Sizeof(arenaHint{}), nil, nil, &memstats.other_sys)

// mspan在alloc时,单元的内存不执行清零操作
h.spanalloc.zero = false

// h->mapcache不需要初始化

// 136个mcentral
for i := range h.central {
// 纪录spanclass、初始化锁
h.central[i].mcentral.init(spanClass(i))
}

// pageAlloc初始化
h.pages.init(&h.lock, &memstats.gcMiscSys, false)
}

// 先清扫并释放至少n个页,然后获取mspan、分配n个页面、更新元信息
func (h *mheap) alloc(npages uintptr, spanclass spanClass) *mspan {
var s *mspan
systemstack(func() {
// 清扫并释放至少n个页

// sweeper数量不为0 => 还在清扫阶段
if !isSweepDone() {
// func (h *mheap) reclaim(npage uintptr)
// 有额度先扣额度,没有额度则按页索引地址找到mspan并清扫,完成至少n个页清扫后返回(分批次清扫,每批512个页)
h.reclaim(npages)
}
// func (h *mheap) allocSpan(npages uintptr, typ spanAllocType, spanclass spanClass) (s *mspan)
// 获取mspan、分配n个页面、更新元信息
s = h.allocSpan(npages, spanAllocHeap, spanclass)
})
return s
}

// 获取mspan、分配n个页面、更新元信息
func (h *mheap) allocManual(npages uintptr, typ spanAllocType) *mspan {
// 类型异常
if !typ.manual() {
throw("manual span allocation called with non-manually-managed type")
}
// sizeclass=0
// 获取mspan、分配n个页面、更新元信息
return h.allocSpan(npages, typ, 0)
}

// heap扩容至少npage,返回实际扩容量和是否成功
func (h *mheap) grow(npage uintptr) (uintptr, bool) {
// 空函数(staticlockranking默认为false)
assertLockHeld(&h.lock)

// 总字节数 = npage按512的倍数向上取整,再乘于每页字节数8192
ask := alignUp(npage, pallocChunkPages) * pageSize

// 实际扩容量
totalGrowth := uintptr(0)
// end地址
end := h.curArena.base + ask
// 按physPageSize的倍数向上取整
nBase := alignUp(end, physPageSize)
// 不够用,不管有没有溢出
if nBase > h.curArena.end || end < h.curArena.base {
// func (h *mheap) sysAlloc(n uintptr, hintList **arenaHint, register bool) (v unsafe.Pointer, size uintptr)
// 向系统申请size大小内存(Reserved),最低64MB,创建arenaHint、heapArena
av, asize := h.sysAlloc(ask, &h.arenaHints, true)

// 申请失败
if av == nil {
// 打印,返回
inUse := gcController.heapFree.load() + gcController.heapReleased.load() + gcController.heapInUse.load()
print("runtime: out of memory: cannot allocate ", ask, "-byte block (", inUse, " in use)\n")
return 0, false
}

// 申请成功

// 同一个arena
if uintptr(av) == h.curArena.end {
// 更新end
h.curArena.end = uintptr(av) + asize
} else {
// 可能有多个arena

// 当前arena剩余空间
if size := h.curArena.end - h.curArena.base; size != 0 {
// 使用sysMap直接映射,内存状态从Reserved改为Prepared
sysMap(unsafe.Pointer(h.curArena.base), size, &gcController.heapReleased)

// statsSeq计数器加1 => 奇数,表示p正在写入stats
// =stats[gen%3] => heapStatsDelta
stats := memstats.heapStats.acquire()
// 剩余内存大小加到released
atomic.Xaddint64(&stats.released, int64(size))
// statsSeq计数器加1 => 偶数
memstats.heapStats.release()
// func (p *pageAlloc) grow(base, size uintptr)
// 分配物理内存,更新并重新统计chunks信息,最后更新summary(Reserved->Prepared->Ready)
h.pages.grow(h.curArena.base, size)
// 累计到实际扩容量
totalGrowth += size
}

// 切换到最新的arena
h.curArena.base = uintptr(av)
h.curArena.end = uintptr(av) + asize
}

// 再算一遍
nBase = alignUp(h.curArena.base+ask, physPageSize)
}

// 原base
v := h.curArena.base
// 新base
h.curArena.base = nBase

// 使用sysMap直接映射,内存状态从Reserved改为Prepared
sysMap(unsafe.Pointer(v), nBase-v, &gcController.heapReleased)

// statsSeq计数器加1 => 奇数,表示p正在写入stats
// =stats[gen%3] => heapStatsDelta
stats := memstats.heapStats.acquire()
// 申请内存大小加到released
atomic.Xaddint64(&stats.released, int64(nBase-v))
// statsSeq计数器加1 => 偶数
memstats.heapStats.release()

// func (p *pageAlloc) grow(base, size uintptr)
// 分配物理内存,更新并重新统计chunks信息,最后更新summary(Reserved->Prepared->Ready)
h.pages.grow(v, nBase-v)
// 累计到实际扩容量
totalGrowth += nBase - v
return totalGrowth, true
}

// 尝试从mspancache末尾拿一个mspan,不触发扩容操作
func (h *mheap) tryAllocMSpan() *mspan {
// p
pp := getg().m.p.ptr()

// 没有p or mspancache为空
if pp == nil || pp.mspancache.len == 0 {
return nil
}

// 从mspancache末尾拿一个mspan
s := pp.mspancache.buf[pp.mspancache.len-1]
pp.mspancache.len--
return s
}

// 从mspancache末尾拿一个mspan(不足时扩容)
func (h *mheap) allocMSpanLocked() *mspan {
// 空函数(staticlockranking默认为false)
assertLockHeld(&h.lock)

// p
pp := getg().m.p.ptr()
// 没有p
if pp == nil {
// spanalloc分配一个mspan header
return (*mspan)(h.spanalloc.alloc())
}

// mspancache为空
if pp.mspancache.len == 0 {
// 生成buf长度一半数量的mspan header
const refillCount = len(pp.mspancache.buf) / 2
for i := 0; i < refillCount; i++ {
pp.mspancache.buf[i] = (*mspan)(h.spanalloc.alloc())
}
// 更新len
pp.mspancache.len = refillCount
}

// 从mspancache末尾拿一个mspan
s := pp.mspancache.buf[pp.mspancache.len-1]
pp.mspancache.len--
return s
}

// 获取mspan、分配n个页面、更新元信息
func (h *mheap) allocSpan(npages uintptr, typ spanAllocType, spanclass spanClass) (s *mspan) {
gp := getg()
// 起始地址,被清理的页数(一般为0)
base, scav := uintptr(0), uintptr(0)
growth := uintptr(0)

// p
pp := gp.m.p.ptr()

// needPhysPageAlign默认false,所以固定返回true
// 有p and n < 16(=64/4)
if !needPhysPageAlign && pp != nil && npages < pageCachePages/4 {
// p.pcache
c := &pp.pcache

// pcache的64个页都用完了
if c.empty() {
lock(&h.lock)
// 通过summary查找,找到一个最少包含一个可用页的块(共64个页信息)
*c = h.pages.allocToCache()
unlock(&h.lock)
}

// func (c *pageCache) alloc(npages uintptr) (uintptr, uintptr)
// 从pcache找到连续n个页的起始地址,并判断这几个页是否被清理过(重用)
base, scav = c.alloc(npages)
// 成功
if base != 0 {
// 尝试从mspancache末尾拿一个mspan,不触发扩容操作
s = h.tryAllocMSpan()
// 成功
if s != nil {
goto HaveSpan
}
// 失败
}
}

// 内存分配失败

// 加锁
lock(&h.lock)

// 从pcache分配页失败
if base == 0 {
// func (p *pageAlloc) alloc(npages uintptr) (addr uintptr, scav uintptr)
// pageAlloc通过summary扫描寻找足以容纳n个页的地址
base, scav = h.pages.alloc(npages)
// 没找到
if base == 0 {
var ok bool
// func (h *mheap) grow(npage uintptr) (uintptr, bool)
// heap扩容至少npage,返回实际扩容量和是否成功(Reserved->Prepared->Ready)
growth, ok = h.grow(npages)

// 扩容失败
if !ok {
unlock(&h.lock)
return nil
}

// 扩容成功,重试

// func (p *pageAlloc) alloc(npages uintptr) (addr uintptr, scav uintptr)
// pageAlloc通过summary扫描寻找足以容纳n个页的地址
base, scav = h.pages.alloc(npages)
// 还是失败,异常
if base == 0 {
throw("grew heap, but no adequate free space found")
}
}
}

// mspan为nil
if s == nil {
// 从mspancache末尾拿一个mspan(不足时扩容)
s = h.allocMSpanLocked()
}
// 解锁
unlock(&h.lock)

// 到这里,page和mspan都准备好了
HaveSpan:

// 内存超限溢出的数量
bytesToScavenge := uintptr(0)
// 是否强制回收
forceScavenge := false

// CPU不限制GC使用
if limit := gcController.memoryLimit.Load(); !gcCPULimiter.limiting() {
// 已映射且可用的内存量(总内存)
inuse := gcController.mappedReady.Load()
// 重用的内存量+已映射且可用的内存量(总内存)超过限制
if uint64(scav)+inuse > uint64(limit) {
// 溢出量
bytesToScavenge = uintptr(uint64(scav) + inuse - uint64(limit))
// 强制回收
forceScavenge = true
}
}

// GC触发临界点有设置限制 and mheap扩容了
if goal := scavenge.gcPercentGoal.Load(); goal != ^uint64(0) && growth > 0 {
// retained=heapInUse+heapFree(gcController)
// 如果heap内存+扩容量超过临界点
if retained := heapRetained(); retained+uint64(growth) > goal {
// 溢出量,先按扩容量为准
todo := growth
// 扩容量比溢出量大
if overage := uintptr(retained + uint64(growth) - goal); todo > overage {
// 实际溢出量(按道理,一般都会走这个流程)
todo = overage
}
// =max(bytesToScavenge,todo) => 哪个大就已哪个为准
if todo > bytesToScavenge {
bytesToScavenge = todo
}
}
}

var now int64
// 有p and 内存超限
if pp != nil && bytesToScavenge > 0 {
// 当前时刻
start := nanotime()
// stamp存储limiterEventScavengeAssist和start
track := pp.limiterEvent.start(limiterEventScavengeAssist, start)

// func (p *pageAlloc) scavenge(nbytes uintptr, shouldStop func() bool, force bool) uintptr
// 回收指定字节数量的内存
released := h.pages.scavenge(bytesToScavenge, func() bool {
// CPU是否限制GC使用
return gcCPULimiter.limiting()
}, forceScavenge)

// releasedEager+=released
mheap_.pages.scav.releasedEager.Add(released)

now = nanotime()
if track {
// 重置stamp字段,纪录耗时
pp.limiterEvent.stop(limiterEventScavengeAssist, now)
}
// 纪录耗时
scavenge.assistTime.Add(now - start)
}

// 更新mspan、arena
h.initSpan(s, typ, spanclass, base, npages)

// 字节数=npages * 8192
nbytes := npages * pageSize
if scav != 0 {
// 内存状态从Prepared改为Ready
sysUsed(unsafe.Pointer(base), nbytes, scav)
// heap内存释放量
gcController.heapReleased.add(-int64(scav))
}

// heap内存可复用量(这里需要减去释放回OS的量)
gcController.heapFree.add(-int64(nbytes - scav))
// heap内存
if typ == spanAllocHeap {
// 累计到heap内存使用量
gcController.heapInUse.add(int64(nbytes))
}

// statsSeq计数器加1 => 奇数,表示p正在写入stats
// =stats[gen%3] => heapStatsDelta
stats := memstats.heapStats.acquire()
atomic.Xaddint64(&stats.committed, int64(scav))
atomic.Xaddint64(&stats.released, -int64(scav))
switch typ {
case spanAllocHeap: // 0-heap内存
atomic.Xaddint64(&stats.inHeap, int64(nbytes))
case spanAllocStack: // 1-stack内存
atomic.Xaddint64(&stats.inStacks, int64(nbytes))
case spanAllocPtrScalarBits: // 3-GC bitmap
atomic.Xaddint64(&stats.inPtrScalarBits, int64(nbytes))
case spanAllocWorkBuf: // 4-GC wbuf
atomic.Xaddint64(&stats.inWorkBufs, int64(nbytes))
}
// statsSeq计数器加1 => 偶数
memstats.heapStats.release()

return s
}

// 更新mspan、arena
func (h *mheap) initSpan(s *mspan, typ spanAllocType, spanclass spanClass, base, npages uintptr) {
// 简单初始化,数据记录到startAddr、npages
s.init(base, npages)
// heapArena分配地址空间,如果开始地址重用则needZero为true
if h.allocNeedsZero(base, npages) {
s.needzero = 1
}

// 字节数=npages * 8192
nbytes := npages * pageSize

// 以下是mspan元信息初始化

// 手动分配管理
if typ.manual() {
s.manualFreeList = 0
s.nelems = 0
s.limit = s.base() + s.npages*pageSize
s.state.set(mSpanManual)
} else {
// heap类型

s.spanclass = spanclass
// >32KB
if sizeclass := spanclass.sizeclass(); sizeclass == 0 {
s.elemsize = nbytes
s.nelems = 1
s.divMul = 0
} else {
// <=32KB

// 向上取整,如原size=20,经过计算sizeclass=3,新size=24
s.elemsize = uintptr(class_to_size[sizeclass])
// scan and elemsize<=512B
if !s.spanclass.noscan() && heapBitsInSpan(s.elemsize) {
// =(总字节数-bitmap字节数)/elemsize
s.nelems = uint16((nbytes - (nbytes / goarch.PtrSize / 8)) / s.elemsize)
} else {
// noscan or elemsize>512B
// 不需要加8个字节的header
s.nelems = uint16(nbytes / s.elemsize)
}
// 用于优化除法运算
s.divMul = class_to_divmagic[sizeclass]
}

// 默认为0
s.freeindex = 0
// 默认为0
s.freeIndexForScan = 0
// 默认64位全为1
s.allocCache = ^uint64(0)
// 从gcBitsArenas分配足以容纳nelems个位的内存(64的倍数向上取整)
s.gcmarkBits = newMarkBits(uintptr(s.nelems))
// 从gcBitsArenas分配足以容纳nelems个位的内存(64的倍数向上取整)
s.allocBits = newAllocBits(uintptr(s.nelems))

// 使用mheap的sweepgen更新mspan的
atomic.Store(&s.sweepgen, h.sweepgen)

// 状态设置
s.state.set(mSpanInUse)
}

// mspan初始化完毕

// ha.spans纪录heapArena内每个页对应的mspan
h.setSpans(s.base(), npages, s)

// 非手动分配管理
if !typ.manual() {
// 通过地址计算出heapArena、页起始索引、页起始位
arena, pageIdx, pageMask := pageIndexOf(s.base())
// 汇编,按位或,看起来只有第一个页有设置
atomic.Or8(&arena.pageInUse[pageIdx], pageMask)
// heap内存页使用量(mSpanInUse) => pagesInUse+=n
h.pagesInUse.Add(npages)
}

// 汇编,linux+amd64下为空函数
publicationBarrier()
}

// 有额度先扣额度,没有额度则按页索引地址找到mspan并清扫,完成至少n个页清扫后返回(分批次清扫,每批512个页)
func (h *mheap) reclaim(npage uintptr) {
// scavenger-回收器已完成工作
if h.reclaimIndex.Load() >= 1<<63 {
return
}

// 防止抢占
mp := acquirem()

// allArenas快照 => []arenaIdx
arenas := h.sweepArenas
// 是否已加锁
locked := false

// 清扫至少n个页面
for npage > 0 {
// 还有额度
if credit := h.reclaimCredit.Load(); credit > 0 {
take := credit
// 额度足够
if take > npage {
// 以n为准
take = npage
}
// 拿走take额度
if h.reclaimCredit.CompareAndSwap(credit, credit-take) {
// 调整,结果>=0
npage -= take
}
continue
}

// 没有额度了

// 获取reclaimIndex => idx=reclaimIndex; reclaimIndex+=512
idx := uintptr(h.reclaimIndex.Add(pagesPerReclaimerChunk) - pagesPerReclaimerChunk)
// 获取heapArena的索引并判断是否越界 => idx/8192 >= len(arenas)
if idx/pagesPerArena >= uintptr(len(arenas)) {
// 越界了
// 设置为1 << 63
h.reclaimIndex.Store(1 << 63)
break
}

// 加锁,用于访问reclaimChunk
if !locked {
lock(&h.lock)
locked = true
}

// 按页索引地址找到mspan并清扫,完成至少512个页清扫后返回
nfound := h.reclaimChunk(arenas, idx, pagesPerReclaimerChunk)
if nfound <= npage {
npage -= nfound
} else {
// 释放了过多的页,则多余的量累计到回收额度
h.reclaimCredit.Add(nfound - npage)
npage = 0
}
}
// 复原锁状态
if locked {
unlock(&h.lock)
}

releasem(mp)
}

// 按页索引地址找到mspan并清扫,完成至少n个页清扫后返回
func (h *mheap) reclaimChunk(arenas []arenaIdx, pageIdx, n uintptr) uintptr {
// 空函数(staticlockranking默认为false)
// 防止访问到过期的mspan指针
assertLockHeld(&h.lock)

n0 := n // 512

var nFreed uintptr
// 计数器加1,返回mheap_.sweepgen及sweepDrainedMask标记是否已设置
sl := sweep.active.begin()
// sweeper数量为0 => sweep阶段结束
if !sl.valid {
return 0
}

for n > 0 {
// =arenas[reclaimIndex/8192]
ai := arenas[pageIdx/pagesPerArena]
// heapArena
ha := h.arenas[ai.l1()][ai.l2()]

// 在heapArena内的页索引=reclaimIndex%8192
arenaPage := uint(pageIdx % pagesPerArena)

// 下面两个都是bitmap,每个都有8K个位,找到n个页的bitmap区域
inUse := ha.pageInUse[arenaPage/8:]
marked := ha.pageMarks[arenaPage/8:]
// uint8类型,可管理8个页
if uintptr(len(inUse)) > n/8 {
inUse = inUse[:n/8]
marked = marked[:n/8]
}

// 遍历每个字节
for i := range inUse {
// 找出标记为已使用但没有被GC标记的页(白色对象,应该要清扫的页)
inUseUnmarked := atomic.Load8(&inUse[i]) &^ marked[i]
// 没有,继续
if inUseUnmarked == 0 {
continue
}

// 有需要清扫的页,找到具体的页
for j := uint(0); j < 8; j++ {
// 找到了
if inUseUnmarked&(1<<j) != 0 {
// 根据页ID找到mspan
s := ha.spans[arenaPage+uint(i)*8+j]
// 尝试获得mspan的所有权
if s, ok := sl.tryAcquire(s); ok {
npages := s.npages
// 解锁
unlock(&h.lock)
// func (sl *sweepLocked) sweep(preserve bool) bool
// 清扫一个mspan(不保留,被heap回收)
if s.sweep(false) {
nFreed += npages
}
// 重新加锁
lock(&h.lock)
// double-check,防止过期mspan指针
inUseUnmarked = atomic.Load8(&inUse[i]) &^ marked[i]
}
}
}
}

// 共扫描inUse*8个页
// 移动索引
pageIdx += uintptr(len(inUse) * 8)
// 计数器调整,看是否还需要继续清扫
n -= uintptr(len(inUse) * 8)
}
// 清扫完毕
// 计数器减1
sweep.active.end(sl)

// 空函数(staticlockranking默认为false)
assertLockHeld(&h.lock)
return nFreed
}

// 释放页和mspan,仅heap管理用
func (h *mheap) freeSpan(s *mspan) {
systemstack(func() {
lock(&h.lock)
// 释放页和mspan
h.freeSpanLocked(s, spanAllocHeap)
unlock(&h.lock)
})
}

// 释放页和mspan,仅手动分配用
func (h *mheap) freeManual(s *mspan, typ spanAllocType) {
// 表示需要清0
s.needzero = 1
lock(&h.lock)
// 释放页和mspan
h.freeSpanLocked(s, typ)
unlock(&h.lock)
}

// 释放页和mspan
func (h *mheap) freeSpanLocked(s *mspan, typ spanAllocType) {
// 空函数(staticlockranking默认为false)
assertLockHeld(&h.lock)

switch s.state.get() {
case mSpanManual: // 手动
if s.allocCount != 0 {
throw("mheap.freeSpanLocked - invalid stack free")
}
case mSpanInUse: // heap管理
if s.isUserArenaChunk {
throw("mheap.freeSpanLocked - invalid free of user arena chunk")
}
if s.allocCount != 0 || s.sweepgen != h.sweepgen {
print("mheap.freeSpanLocked - span ", s, " ptr ", hex(s.base()), " allocCount ", s.allocCount, " sweepgen ", s.sweepgen, "/", h.sweepgen, "\n")
throw("mheap.freeSpanLocked - invalid free")
}
// heap内存页使用量(mSpanInUse) => pagesInUse-=n
h.pagesInUse.Add(-s.npages)

// 通过地址计算出heapArena、页起始索引、页起始位
arena, pageIdx, pageMask := pageIndexOf(s.base())
// 在pageInUse中将该页所在位设置为0
atomic.And8(&arena.pageInUse[pageIdx], ^pageMask)
default: // 其他
throw("mheap.freeSpanLocked - invalid span state")
}

// 更新stat

// 总字节数
nbytes := s.npages * pageSize
// heap内存可复用量(free时增加)
gcController.heapFree.add(int64(nbytes))
// heap内存
if typ == spanAllocHeap {
// 累计到heap内存使用量
gcController.heapInUse.add(-int64(nbytes))
}

// statsSeq计数器加1 => 奇数,表示p正在写入stats
// =stats[gen%3] => heapStatsDelta
stats := memstats.heapStats.acquire()
switch typ {
case spanAllocHeap: // 0-heap内存
atomic.Xaddint64(&stats.inHeap, -int64(nbytes))
case spanAllocStack: // 1-stack内存
atomic.Xaddint64(&stats.inStacks, -int64(nbytes))
case spanAllocPtrScalarBits: // 3-GC bitmap
atomic.Xaddint64(&stats.inPtrScalarBits, -int64(nbytes))
case spanAllocWorkBuf: // 4-GC wbuf
atomic.Xaddint64(&stats.inWorkBufs, -int64(nbytes))
}
// statsSeq计数器加1 => 偶数
memstats.heapStats.release()

// 更新并重新统计chunks,最后更新summary(释放)
h.pages.free(s.base(), s.npages)

// 默认状态
s.state.set(mSpanDead)
// mspan放到mspancache末尾
h.freeMSpanLocked(s)
}

// mspan放到mspancache末尾
func (h *mheap) freeMSpanLocked(s *mspan) {
// 空函数(staticlockranking默认为false)
assertLockHeld(&h.lock)

// p
pp := getg().m.p.ptr()

// p不为空 and buf不满一半
if pp != nil && pp.mspancache.len < len(pp.mspancache.buf) {
// 放到mspancache末尾
pp.mspancache.buf[pp.mspancache.len] = s
pp.mspancache.len++
return
}

// 没有p
h.spanalloc.free(unsafe.Pointer(s))
}

// 向系统申请size大小内存(Reserved),最低64MB,创建arenaHint、heapArena(这是mheap的方法)
func (h *mheap) sysAlloc(n uintptr, hintList **arenaHint, register bool) (v unsafe.Pointer, size uintptr) {
// 空函数(staticlockranking默认为false)
assertLockHeld(&h.lock)

// 按64MB的倍数向上取整
n = alignUp(n, heapArenaBytes)

// 一般情况下都是
if hintList == &h.arenaHints {
// 忽略,这一步会失败,因为arena压根没初始化
v = h.arena.alloc(n, heapArenaBytes, &gcController.heapReleased)
if v != nil {
size = n
goto mapped
}
}

for *hintList != nil {
hint := *hintList
// 初始地址
p := hint.addr
// 向下扩展
if hint.down {
p -= n
}

if p+n < p {
// 溢出
v = nil
} else if arenaIndex(p+n-1) >= 1<<arenaBits {
// 地址越界
v = nil
} else {
// 向系统申请n大小的内存(64MB的倍数,Reserved)
v = sysReserve(unsafe.Pointer(p), n)
}
// 申请成功
if p == uintptr(v) {
// 向上扩展
if !hint.down {
p += n
}
// 更新addr
hint.addr = p
size = n
// 退出循环
break
}

// 下面是申请失败

if v != nil {
// 释放未使用的内存回收给操作系统
sysFreeOS(v, n)
}
// 下一个hint
*hintList = hint.next
// hint回收放到free链表
h.arenaHintAlloc.free(unsafe.Pointer(hint))
}

// 所有的hint都失败了,申请64MB内存,但生成两个hint
if size == 0 {
// 默认false,忽略
if raceenabled {
throw("too many address space collisions for -race mode")
}

// 向系统申请n大小的内存(Reserved),按64MB对齐,返回对齐后的内存地址和大小,对齐时剩余的量全部释放回系统
v, size = sysReserveAligned(nil, n, heapArenaBytes)
// 还是失败
if v == nil {
return nil, 0
}

// 成功

// 第一个新hint
hint := (*arenaHint)(h.arenaHintAlloc.alloc())
hint.addr, hint.down = uintptr(v), true
hint.next, mheap_.arenaHints = mheap_.arenaHints, hint

// 第二个新hint
hint = (*arenaHint)(h.arenaHintAlloc.alloc())
hint.addr = uintptr(v) + size
hint.next, mheap_.arenaHints = mheap_.arenaHints, hint
}

// 检查指针
{
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 != "" {
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")
}
}

// 低26位不为0
if uintptr(v)&(heapArenaBytes-1) != 0 {
throw("misrounded allocation in sysAlloc")
}

// 内存申请成功

mapped:
// 创建heapArena
for ri := arenaIndex(uintptr(v)); ri <= arenaIndex(uintptr(v)+size-1); ri++ {
// l2数组,4M个heapArena指针
l2 := h.arenas[ri.l1()]
// l2为nil
if l2 == nil {
// 直接向系统申请4M*8B=32MB内存(Ready)
l2 = (*[1 << arenaL2Bits]*heapArena)(sysAllocOS(unsafe.Sizeof(*l2)))
// 申请失败
if l2 == nil {
throw("out of memory allocating heap arena map")
}

if h.arenasHugePages {
// 按physHugePageSize大小对齐,并尝试转成huge page(只有linux有)
sysHugePage(unsafe.Pointer(l2), unsafe.Sizeof(*l2))
} else {
// 不使用huge page
sysNoHugePage(unsafe.Pointer(l2), unsafe.Sizeof(*l2))
}
// h.arenas[ri.l1()]=l2
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), goarch.PtrSize, &memstats.gcMiscSys))
if r == nil {
// 调用sysAlloc向系统申请64KB大小的内存(Ready),按8倍数向上取整,统计
// 超过64KB直接向系统申请,未超过64KB则一次性申请256KB内存后再分配
r = (*heapArena)(persistentalloc(unsafe.Sizeof(*r), goarch.PtrSize, &memstats.gcMiscSys))
// 失败,异常
if r == nil {
throw("out of memory allocating heap arena metadata")
}
}

// 如果需要注册到allArenas
if register {
// 容量不足
if len(h.allArenas) == cap(h.allArenas) {
// 总字节数(双倍容量)
size := 2 * uintptr(cap(h.allArenas)) * goarch.PtrSize
// 最小为一个页大小
if size == 0 {
size = physPageSize
}
// 调用sysAlloc向系统申请64KB大小的内存(Ready),按align倍数向上取整,统计
// 超过64KB直接向系统申请,未超过64KB则一次性申请256KB内存后再分配
newArray := (*notInHeap)(persistentalloc(size, goarch.PtrSize, &memstats.gcMiscSys))
// 申请失败
if newArray == nil {
throw("out of memory allocating allArenas")
}
// 旧数组
oldSlice := h.allArenas
// 替换为新数组
*(*notInHeapSlice)(unsafe.Pointer(&h.allArenas)) = notInHeapSlice{newArray, len(h.allArenas), int(size / goarch.PtrSize)}
// 复制旧数组内容
copy(h.allArenas, oldSlice)
// 不释放旧数组,可能存在并发读
}
// 放到数组末尾
h.allArenas = h.allArenas[:len(h.allArenas)+1]
h.allArenas[len(h.allArenas)-1] = ri
}
// l2[ri.l2()] = r
atomic.StorepNoWB(unsafe.Pointer(&l2[ri.l2()]), unsafe.Pointer(r))
}

return
}

// ha.spans纪录heapArena内每个页对应的mspan
func (h *mheap) setSpans(base, npage uintptr, s *mspan) {
// = base/8192
p := base / pageSize
// heapArena索引
ai := arenaIndex(base)
// heapArena
ha := h.arenas[ai.l1()][ai.l2()]
// 这几个页全局都要纪录mspan指针
for n := uintptr(0); n < npage; n++ {
// 索引=(p+n)%8192
i := (p + n) % pagesPerArena
// 末尾/到了一个新的heapArena
if i == 0 {
// 重新读
ai = arenaIndex(base + n*pageSize)
ha = h.arenas[ai.l1()][ai.l2()]
}
// heapArena纪录mspan
ha.spans[i] = s
}
}

// spanalloc分配的mspan纪录到allspans
func recordspan(vh unsafe.Pointer, p unsafe.Pointer) {
// mheap
h := (*mheap)(vh)
// mspan
s := (*mspan)(p)

// 空函数(staticlockranking默认为false)
assertLockHeld(&h.lock)

// 扩容
if len(h.allspans) >= cap(h.allspans) {
// 8KB
n := 64 * 1024 / goarch.PtrSize
// 1.5倍
if n < cap(h.allspans)*3/2 {
n = cap(h.allspans) * 3 / 2
}
var new []*mspan
sp := (*slice)(unsafe.Pointer(&new))
// 直接向系统申请8*n内存(Ready)
sp.array = sysAlloc(uintptr(n)*goarch.PtrSize, &memstats.other_sys)
// 申请失败
if sp.array == nil {
throw("runtime: cannot allocate memory")
}
// 旧len
sp.len = len(h.allspans)
// 新cap
sp.cap = n
// 复制
if len(h.allspans) > 0 {
copy(new, h.allspans)
}
oldAllspans := h.allspans
// 替换旧allspans
*(*notInHeapSlice)(unsafe.Pointer(&h.allspans)) = *(*notInHeapSlice)(unsafe.Pointer(&new))
// 释放内存回系统
if len(oldAllspans) != 0 {
sysFree(unsafe.Pointer(&oldAllspans[0]), uintptr(cap(oldAllspans))*unsafe.Sizeof(oldAllspans[0]), &memstats.other_sys)
}
}
// 放到末尾
h.allspans = h.allspans[:len(h.allspans)+1]
h.allspans[len(h.allspans)-1] = s
}

// heapArena分配地址空间,如果开始地址重用则needZero为true
func (h *mheap) allocNeedsZero(base, npage uintptr) (needZero bool) {
for npage > 0 {
// 转换为anera索引
ai := arenaIndex(base)
// 找到heapArena
ha := h.arenas[ai.l1()][ai.l2()]

// 已清0的索引
zeroedBase := atomic.Loaduintptr(&ha.zeroedBase)
// 在heapArena内部的地址=base%64MB
arenaBase := base % heapArenaBytes
// 重用,需要清0(zeroedBase后面的区域都为0)
if arenaBase < zeroedBase {
needZero = true
}

// 终止地址=arenaBase+npage*8192
arenaLimit := arenaBase + npage*pageSize
// 最大64MB
if arenaLimit > heapArenaBytes {
arenaLimit = heapArenaBytes
}
// 如果limit超过zerobase,则替换掉zeroedBase
for arenaLimit > zeroedBase {
// ha.zeroedBase设置为arenaLimit
if atomic.Casuintptr(&ha.zeroedBase, zeroedBase, arenaLimit) {
break
}
// 重新读一遍
zeroedBase = atomic.Loaduintptr(&ha.zeroedBase)
// double-check
if zeroedBase <= arenaLimit && zeroedBase > arenaBase {
// 有交叉
throw("potentially overlapping in-use allocations detected")
}
}

// base调整
base += arenaLimit - arenaBase
// 这n个页可能分为两个heapArena,第一个计算完了,计算下一个
npage -= (arenaLimit - arenaBase) / pageSize
}
return
}

// runtime/debug.freeOSMemory,手动管理
// 回收指定字节数量的内存,期间禁止malloc
func (h *mheap) scavengeAll() {
gp := getg()
// 禁止malloc
gp.m.mallocing++

// func (p *pageAlloc) scavenge(nbytes uintptr, shouldStop func() bool, force bool) uintptr
// 回收指定字节数量的内存
released := h.pages.scavenge(^uintptr(0), nil, true)

// 重置,允许malloc
gp.m.mallocing--

// debug,忽略
if debug.scavtrace > 0 {
printScavTrace(0, released, true)
}
}

// 相关内存如heapArena、chunk元素重新按huge_page的大小对齐(linux才有,不一定成功)
func (h *mheap) enableMetadataHugePages() {
// chunk元素按physHugePageSize大小对齐,并尝试转成huge page(只有linux有)
h.pages.enableChunkHugePages()

lock(&h.lock)
// 已设置
if h.arenasHugePages {
unlock(&h.lock)
return
}
// 设置
h.arenasHugePages = true
unlock(&h.lock)

// 遍历arenas
for i := range h.arenas {
// l2数组,4MB个数据
l2 := (*[1 << arenaL2Bits]*heapArena)(atomic.Loadp(unsafe.Pointer(&h.arenas[i])))
if l2 == nil {
continue
}
// 按physHugePageSize大小对齐,并尝试转成huge page(只有linux有)
sysHugePage(unsafe.Pointer(l2), unsafe.Sizeof(*l2))
}
}

mcentral

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
// mcentral初始化
func (c *mcentral) init(spc spanClass) {
// 纪录spanclass
c.spanclass = spc
// 锁初始化
lockInit(&c.partial[0].spineLock, lockRankSpanSetSpine)
lockInit(&c.partial[1].spineLock, lockRankSpanSetSpine)
lockInit(&c.full[0].spineLock, lockRankSpanSetSpine)
lockInit(&c.full[1].spineLock, lockRankSpanSetSpine)
}

// 尝试从mcentral获取一个mspan,没有则从mheap获取,更新mspan状态并返回
func (c *mcentral) cacheSpan() *mspan {
// 计算mspan大小(字节)= 页数 * 8KB
spanBytes := uintptr(class_to_allocnpages[c.spanclass.sizeclass()]) * _PageSize

// 分配内存时,如果sweeper还在清扫中且分配速度比清扫速度快,则协助sweeper清扫
deductSweepCredit(spanBytes, 0)

// 循环总预算
spanBudget := 100

var s *mspan
var sl sweepLocker

sg := mheap_.sweepgen
// =partial[sweepgen/2%2]
// 尝试从已清扫有空对象的spanSet里获取一个mspan => partial[sweepgen/2%2]
if s = c.partialSwept(sg).pop(); s != nil {
// 找到了
goto havespan
}

// 计数器加1,返回mheap_.sweepgen及sweepDrainedMask标记是否已设置
sl = sweep.active.begin()
// sweeper数量不为0 => 还在清扫阶段
if sl.valid {
// 100次
for ; spanBudget >= 0; spanBudget-- {
// =partial[1-sweepgen/2%2]
// 尝试从未清扫有空对象的spanSet里获取一个mspan
s = c.partialUnswept(sg).pop()
// partial为空
if s == nil {
break
}

// partial不为空

// 尝试获得mspan的所有权
if s, ok := sl.tryAcquire(s); ok {
// 清扫一个mspan(保留)
s.sweep(true)
// 计数器减1
sweep.active.end(sl)
// 找到了
goto havespan
}

// 获取mspan所有权失败,被其他工作线程清扫中,尝试别的mspan
}

// 尝试从partial获取mspan失败

// 100次
for ; spanBudget >= 0; spanBudget-- {
// 尝试从未清扫无空对象的spanSet里获取一个mspan
s = c.fullUnswept(sg).pop()
// full为空
if s == nil {
break
}

// 尝试获得mspan的所有权
if s, ok := sl.tryAcquire(s); ok {
// 清扫一个mspan(保留)
s.sweep(true)
// 获取freeindex
freeIndex := s.nextFreeIndex()
// 有空余空间
if freeIndex != s.nelems {
// 更新freeindex
s.freeindex = freeIndex
// 计数器减1
sweep.active.end(sl)
//
goto havespan
}

// 清扫后,mspan放到full[sweepgen/2%2]
c.fullSwept(sg).push(s.mspan)
}
}

// 尝试从full获取mspan失败

// 计数器减1
sweep.active.end(sl)
}

// 没有从partial、full找到一个mspan

// func (c *mcentral) grow() *mspan
// 从mheap分配内存创建一个mspan(Reserved->Prepared->Ready)
s = c.grow()
// 分配失败
if s == nil {
return nil
}

// 到这里,mspan准备就绪

havespan:

// 计算剩余空间
n := int(s.nelems) - int(s.allocCount)
// mspan没有剩余空间
if n == 0 || s.freeindex == s.nelems || s.allocCount == s.nelems {
throw("span has no free objects")
}

// 按64倍数对齐
freeByteBase := s.freeindex &^ (64 - 1)
// 计算bitmap字节数
whichByte := freeByteBase / 8
// 从s.allocBits分配8个字节替换为新的s.allocCache
s.refillAllocCache(whichByte)

// 根据freeindex调整,丢弃已用位
s.allocCache >>= s.freeindex % 64

return s
}

// mspan已过期则清扫,未过期则根据是否mspan释放有剩余放到partial或full链表
func (c *mcentral) uncacheSpan(s *mspan) {
// 异常,已分配对象数为0
if s.allocCount == 0 {
throw("uncaching span but s.allocCount == 0")
}

sg := mheap_.sweepgen
// 已过期 => mspan.sweepgen < mheap_.sweepgen
stale := s.sweepgen == sg+1

if stale {
// 已过期
// 从 sg+1 改为 sg-1
atomic.Store(&s.sweepgen, sg-1)
} else {
// 未过期
// 改为sg
atomic.Store(&s.sweepgen, sg)
}

// 已过期
if stale {
// 封装mspan,获得所有权
ss := sweepLocked{s}
// 清扫一个mspan(不保留,被heap回收)
ss.sweep(false)
} else {
// 未过期

// mspan还有剩余
if int(s.nelems)-int(s.allocCount) > 0 {
// 把mspan放入已清扫有空对象的spanSet => partial[sweepgen/2%2]
c.partialSwept(sg).push(s)
} else {
// mspan无剩余
// full[sweepgen/2%2]
c.fullSwept(sg).push(s)
}
}
}

// 从mheap分配内存创建一个mspan
func (c *mcentral) grow() *mspan {
// 根据spanclass获取该mspan指定的页数
npages := uintptr(class_to_allocnpages[c.spanclass.sizeclass()])
// 根据spanclass获取该mspan的对象大小
size := uintptr(class_to_size[c.spanclass.sizeclass()])

// 先清扫并释放至少n个页,然后获取mspan、分配n个页面、更新元信息
s := mheap_.alloc(npages, c.spanclass)
// 分配失败
if s == nil {
return nil
}

// 计算对象总数 = npages*8KB/size = (npages << _PageShift) / size
n := s.divideByElemSize(npages << _PageShift)
// 终止地址
s.limit = s.base() + size*n
// 初始化bitmap区域
s.initHeapBits()
return s
}

mcache

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
// 创建mcache
func allocmcache() *mcache {
var c *mcache
systemstack(func() {
// mheap加锁
lock(&mheap_.lock)
// 从cachealloc申请160字节作为mcache(内存不够用一次性申请16KB)
c = (*mcache)(mheap_.cachealloc.alloc())
// sweepgen快照
c.flushGen.Store(mheap_.sweepgen)
// 解锁
unlock(&mheap_.lock)
})
// 遍历136个mspan
for i := range c.alloc {
// 全部指向emptymspan全局变量
c.alloc[i] = &emptymspan
}
// 内存分配采样比例(memprof用)
// 返回堆分析的下一个采样点(随机数:[0,MemProfileRate))
c.nextSample = nextSample()
return c
}

// mcache重置放回free链表
func freemcache(c *mcache) {
systemstack(func() {
// tiny、alloc重置并释放mspan,更新内存统计信息
c.releaseAll()
// 清空stackcache
stackcache_clear(c)

lock(&mheap_.lock)
// 放回free链表
mheap_.cachealloc.free(unsafe.Pointer(c))
unlock(&mheap_.lock)
})
}

// tiny、alloc重置并释放mspan,更新内存统计信息
func (c *mcache) releaseAll() {
// 快照,需要被GC扫描的字节数
scanAlloc := int64(c.scanAlloc)
// 重置
c.scanAlloc = 0

sg := mheap_.sweepgen
// 累计存活字节数
dHeapLive := int64(0)
// 所有mpsan清空
for i := range c.alloc {
// mspan
s := c.alloc[i]
// 不为空
if s != &emptymspan {
// 已分配对象数
slotsUsed := int64(s.allocCount) - int64(s.allocCountBeforeCache)
// 重置
s.allocCountBeforeCache = 0

// statsSeq计数器加1 => 奇数,表示p正在写入stats
// =stats[gen%3] => heapStatsDelta
stats := memstats.heapStats.acquire()
// 累计到全局计数器
atomic.Xadd64(&stats.smallAllocCount[spanClass(i).sizeclass()], slotsUsed)
// statsSeq计数器加1 => 偶数
memstats.heapStats.release()

// 累计已分配字节数
gcController.totalAlloc.Add(slotsUsed * int64(s.elemsize))

// 未过期
if s.sweepgen != sg+1 {
// -=剩余可用字节数
dHeapLive -= int64(s.nelems-s.allocCount) * int64(s.elemsize)
}

// mspan已过期则清扫,未过期则根据是否mspan释放有剩余放到partial或full链表
mheap_.central[i].mcentral.uncacheSpan(s)
// 设置为空的mspan
c.alloc[i] = &emptymspan
}
}
// tiny区域清扫
c.tiny = 0
c.tinyoffset = 0

// statsSeq计数器加1 => 奇数,表示p正在写入stats
// =stats[gen%3] => heapStatsDelta
stats := memstats.heapStats.acquire()
// 累计到全局tiny计数器
atomic.Xadd64(&stats.tinyAllocCount, int64(c.tinyAllocs))
// 重置
c.tinyAllocs = 0
// statsSeq计数器加1 => 偶数
memstats.heapStats.release()

//
gcController.update(dHeapLive, scanAlloc)
}

// 协助sweeper清扫,从mheap分配内存、更新索引/gc等信息,mspan放到fullSwept
func (c *mcache) allocLarge(size uintptr, noscan bool) *mspan {
// 溢出
if size+_PageSize < size {
throw("out of memory")
}

// 页数(右移13位,即除于8KB)
npages := size >> _PageShift
// 除后还有余量,n+=1
if size&_PageMask != 0 {
npages++
}

// 分配内存时,如果sweeper还在清扫中且分配速度比清扫速度快,则协助sweeper清扫
deductSweepCredit(npages*_PageSize, npages)

// 组合sizeclass和noscan成spanclass
spc := makeSpanClass(0, noscan)

// func (h *mheap) alloc(npages uintptr, spanclass spanClass) *mspan
// 先清扫并释放至少n个页,然后获取mspan、分配n个页面、更新元信息
s := mheap_.alloc(npages, spc)
// 分配失败
if s == nil {
throw("out of memory")
}

// statsSeq计数器加1 => 奇数,表示p正在写入stats
// =stats[gen%3] => heapStatsDelta
stats := memstats.heapStats.acquire()
// 累计分配字节数
atomic.Xadd64(&stats.largeAlloc, int64(npages*pageSize))
// 累计分配对象数
atomic.Xadd64(&stats.largeAllocCount, 1)
// statsSeq计数器加1 => 偶数
memstats.heapStats.release()

// 累计已分配字节数
gcController.totalAlloc.Add(int64(npages * pageSize))

//
gcController.update(int64(s.npages*pageSize), 0)

// 把mspan放到full[sweepgen/2%2]
mheap_.central[spc].mcentral.fullSwept(mheap_.sweepgen).push(s)
// 终止地址
s.limit = s.base() + size
// 初始化bitmap区域(实际上为大对象时不需要执行)
s.initHeapBits()
return s
}

// 通过64位的allocCache快速判断并分配对象
func nextFreeFast(s *mspan) gclinkptr {
// 64位二进制数的尾部的0的个数
theBit := sys.TrailingZeros64(s.allocCache)

// allocCache>0(初始化时64位全为1)
if theBit < 64 {
// 下一个可用对象索引(二进制中为0的位置需要跳过)
result := s.freeindex + uint16(theBit)
// mspan空间没有用完
if result < s.nelems {
// 更新freeidx,指向下一个可分配对象
freeidx := result + 1
// 64个对象已经分配完(实际上是63个,这里的判断会跳过最后1个可用对象)
if freeidx%64 == 0 && freeidx != s.nelems {
return 0
}
// 丢弃低x+1位(因为这几位已经被分配了)
s.allocCache >>= uint(theBit + 1)
// 更新freeidx
s.freeindex = freeidx
// 计数器allocCount+=1
s.allocCount++
// 返回对象的地址
return gclinkptr(uintptr(result)*s.elemsize + s.base())
}
}
// allocCache=0,没有可分配对象
return 0
}

// mspan获取一个可用对象,如果mspan已满,则从mcentral获取新的mspan替换原mspan后重试
func (c *mcache) nextFree(spc spanClass) (v gclinkptr, s *mspan, checkGCTrigger bool) {
// 根据spanclass从mcache获取mspan
s = c.alloc[spc]
// 是否需要触发GC清扫(分配了新的mspan)
checkGCTrigger = false
// 获取可用对象索引,如果allocCache用完则重新填充再计算索引(可能多次)
freeIndex := s.nextFreeIndex()

// mspan已满
if freeIndex == s.nelems {
// 异常
if s.allocCount != s.nelems {
println("runtime: s.allocCount=", s.allocCount, "s.nelems=", s.nelems)
throw("s.allocCount != s.nelems && freeIndex == s.nelems")
}
// 从mcentral分配新的mspan替换原mspan
c.refill(spc)
// 需要触发GC清扫
checkGCTrigger = true
// 重新读一遍
s = c.alloc[spc]

// 获取可用对象索引,如果allocCache用完则重新填充再计算索引(可能多次)
freeIndex = s.nextFreeIndex()
}

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

// 对象地址
v = gclinkptr(uintptr(freeIndex)*s.elemsize + s.base())
// 计数器allocCount+=1
s.allocCount++
// 异常,超过总量
if s.allocCount > s.nelems {
println("s.allocCount=", s.allocCount, "s.nelems=", s.nelems)
throw("s.allocCount > s.nelems")
}
return
}

// 从mcentral分配新的mspan替换原mspan
func (c *mcache) refill(spc spanClass) {
// 根据spanclass从mcache获取mspan
s := c.alloc[spc]

// 异常,还有可用对象
if s.allocCount != s.nelems {
throw("refill of span with free space remaining")
}

// 已经初始化
if s != &emptymspan {
// 期望状态:已清扫并被mcache使用
if s.sweepgen != mheap_.sweepgen+3 {
throw("bad sweepgen in refill")
}
// 原mspan已过期则清扫,未过期则根据是否mspan释放有剩余放到partial或full链表
mheap_.central[spc].mcentral.uncacheSpan(s)

// 更新heapStats
// statsSeq计数器加1 => 奇数,表示p正在写入stats
// =stats[gen%3] => heapStatsDelta
stats := memstats.heapStats.acquire()

// 已分配对象数
slotsUsed := int64(s.allocCount) - int64(s.allocCountBeforeCache)
// 累计到小对象分配数
atomic.Xadd64(&stats.smallAllocCount[spc.sizeclass()], slotsUsed)

// 如果是tiny分配
if spc == tinySpanClass {
// 累计到微小对象数
atomic.Xadd64(&stats.tinyAllocCount, int64(c.tinyAllocs))
// 重置
c.tinyAllocs = 0
}
// statsSeq计数器加1 => 偶数
memstats.heapStats.release()

// 已分配字节数
bytesAllocated := slotsUsed * int64(s.elemsize)
// 累计已分配字节数
gcController.totalAlloc.Add(bytesAllocated)

// 重置
s.allocCountBeforeCache = 0
}

// 新mspan
// 从mcentral获取一个mspan
s = mheap_.central[spc].mcentral.cacheSpan()
// 分配失败
if s == nil {
throw("out of memory")
}

// 异常,mspan空间用光了
if s.allocCount == s.nelems {
throw("span has no free space")
}

// 已清扫并被mcache使用
s.sweepgen = mheap_.sweepgen + 3

// 放到mcache前,纪录allocCount快照
s.allocCountBeforeCache = s.allocCount

// 更新heapLive
// mspan已使用字节数
usedBytes := uintptr(s.allocCount) * s.elemsize
// mspan剩余可用字节纪录到heapLive,
gcController.update(int64(s.npages*pageSize)-int64(usedBytes), int64(c.scanAlloc))
// 清空scanAlloc
c.scanAlloc = 0

c.alloc[spc] = s
}

mspan

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
// 获取可用对象索引,如果allocCache用完则重新填充再计算索引(可能多次)
func (s *mspan) nextFreeIndex() uint16 {
// 可用对象索引
sfreeindex := s.freeindex
// mspan可存储对象数量
snelems := s.nelems
// mspan已满
if sfreeindex == snelems {
return sfreeindex
}
// 越界
if sfreeindex > snelems {
throw("s.freeindex > s.nelems")
}

// 64位bitmap
aCache := s.allocCache
// allocCache尾部的0的个数(这个时候可能还剩余1个可分配对象)
bitIndex := sys.TrailingZeros64(aCache)
// allocCache为0,全部已分配完
for bitIndex == 64 {
// 按64的倍数向上取整
sfreeindex = (sfreeindex + 64) &^ (64 - 1)
// mspan已满
if sfreeindex >= snelems {
s.freeindex = snelems
return snelems
}

//mspan未满

// 字节数
whichByte := sfreeindex / 8
// 从s.allocBits分配8个字节替换为新的s.allocCache
s.refillAllocCache(whichByte)
// 重新读一遍新的allocCache
aCache = s.allocCache
// 顺利的话,这里的bitIndex应该为0
bitIndex = sys.TrailingZeros64(aCache)

// 还是用完了,继续循环
}

// 可用对象的索引(二进制中为0的位置需要跳过)
result := sfreeindex + uint16(bitIndex)
// mspan已满
if result >= snelems {
s.freeindex = snelems
return snelems
}

// mspan未满

// 丢弃低x+1位(因为这几位已经被分配了)
s.allocCache >>= uint(bitIndex + 1)
// 更新freeidx,指向下一个可分配对象
sfreeindex = result + 1

// 64个对象已经分配完(实际上是63个,这里的判断会跳过最后1个可用对象)
if sfreeindex%64 == 0 && sfreeindex != snelems {
// 字节数
whichByte := sfreeindex / 8
// 从s.allocBits分配8个字节替换为新的s.allocCache
s.refillAllocCache(whichByte)
}
s.freeindex = sfreeindex
return result
}

// 返回封装bitmap区域的slice
func (span *mspan) heapBits() []uintptr {
// 只有一个页
if span.npages == 1 {
return heapBitsSlice(span.base(), pageSize)
}
// 多个页
return heapBitsSlice(span.base(), span.npages*pageSize)
}

// 封装bitmap区域成slice
func heapBitsSlice(spanBase, spanSize uintptr) []uintptr {
// bitmap需要的字节数
bitmapSize := spanSize / goarch.PtrSize / 8
// 有多少个int
elems := int(bitmapSize / goarch.PtrSize)

var sl notInHeapSlice
// 纪录bitmap区域的起始地址、所需int数
sl = notInHeapSlice{(*notInHeap)(unsafe.Pointer(spanBase + spanSize - bitmapSize)), elems, elems}
return *(*[]uintptr)(unsafe.Pointer(&sl))
}

// 初始化bitmap区域
func (s *mspan) initHeapBits() {
// scan and sizeclass==1
if goarch.PtrSize == 8 && !s.spanclass.noscan() && s.spanclass.sizeclass() == 1 {
// 返回封装bitmap区域的slice
b := s.heapBits()
// 全部位设为1
for i := range b {
b[i] = ^uintptr(0)
}
} else if (!s.spanclass.noscan() && heapBitsInSpan(s.elemsize)) || s.isUserArenaChunk {
// (scan && size<=512B) or 手动管理内存
// 返回封装bitmap区域的slice
b := s.heapBits()
// 清0
clear(b)
}
}

// bitmap纪录指针位置,返回需要扫描的字节数
func heapSetTypeNoHeader(x, dataSize uintptr, typ *_type, span *mspan) uintptr {
// bitmap纪录指针位置,返回需要扫描的字节数
scanSize := span.writeHeapBitsSmall(x, dataSize, typ)
return scanSize
}

// bitmap纪录指针位置,返回需要扫描的字节数
func (span *mspan) writeHeapBitsSmall(x, dataSize uintptr, typ *_type) (scanSize uintptr) {
// type Example struct {
// a *int8 // 8B (指针)
// b int16 // 2B (非指针,编译器会对齐b为8字节)
// c *int32 // 8B (指针)
// d float64 // 8B (非指针)
// }

// 获取数据类型的GCMask,如上为0b0101
src0 := readUintptr(getGCMask(typ))

// 需要扫描的总字节数,一般只会比Size_小一点
// 从第一个字段开始到最后一个指针类型字段,如上为24字节
scanSize = typ.PtrBytes

src := src0
if typ.Size_ == goarch.PtrSize {
// 8字节大小,单个指针
// 掩码:(1<<x)-1 => 低位全为1
src = (1 << (dataSize / goarch.PtrSize)) - 1
} else {
// 非8字节大小
// 遍历所有对象
for i := typ.Size_; i < dataSize; i += typ.Size_ {
// 掩码累加(如上,2个Example,结果为 0b01010101)
src |= src0 << (i / goarch.PtrSize)
// 扩展指针扫描范围(如上,2个Example,结果为24+32=56)
scanSize += typ.Size_
}
}

// bitmap内存区域起始地址 => base+8192-128
dst := unsafe.Pointer(span.base() + pageSize - pageSize/goarch.PtrSize/8)
// 指针位置 => sizeclass范围是[0,32],闭区间,分配的页数为1
o := (x - span.base()) / goarch.PtrSize
// 第几个uint64
i := o / ptrBits
// 具体bit位置
j := o % ptrBits
// 指针数量
bits := span.elemsize / goarch.PtrSize
// 超过64位,分成两个uint64处理
if j+bits > ptrBits {
// 第1个uint64要设置的位数量
bits0 := ptrBits - j
// 第2个uint64要设置的位数量
bits1 := bits - bits0
dst0 := (*uintptr)(add(dst, (i+0)*goarch.PtrSize))
dst1 := (*uintptr)(add(dst, (i+1)*goarch.PtrSize))
// src地bits0位纪录到dst0
*dst0 = (*dst0)&(^uintptr(0)>>bits0) | (src << j)
// src高bits1位纪录到dst1
*dst1 = (*dst1)&^((1<<bits1)-1) | (src >> bits0)
} else {
// 不超过64位,只写一个uint64
dst := (*uintptr)(add(dst, i*goarch.PtrSize))
// src纪录到dst
*dst = (*dst)&^(((1<<bits)-1)<<j) | (src << j)
}

return
}

pageCache

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
// 从pcache找到连续n个页的起始地址,并判断这几个页是否被清理过(重用)
func (c *pageCache) alloc(npages uintptr) (uintptr, uintptr) {
// bitmap为0,64个页全部用完
if c.cache == 0 {
return 0, 0
}

// 64个页未用完

// 分配1个页
if npages == 1 {
// cache尾部0个数,定位可用页的位置
i := uintptr(sys.TrailingZeros64(c.cache))
// 丢弃低i位后取最低位
scav := (c.scav >> i) & 1
// 低i位清0,标记该页已使用
c.cache &^= 1 << i
// 低i位清0,标记该页未清理
c.scav &^= 1 << i
// 起始地址,被清理的页数
return c.base + i*pageSize, uintptr(scav) * pageSize
}
// 找出连续n个1的起始位置(64个位),找不到返回64
return c.allocN(npages)
}

// 找出连续n个1的起始位置(64个位),找不到返回64
func (c *pageCache) allocN(npages uintptr) (uintptr, uintptr) {
// 找出连续n个1的起始位置(64个位),找不到返回64
i := findBitRange64(c.cache, uint(npages))
// 64代表没找到
if i >= 64 {
return 0, 0
}

// 找到了

// 低n个1向左移动i位
mask := ((uint64(1) << npages) - 1) << i
// 计算1的数量
scav := sys.OnesCount64(c.scav & mask)
// 这连续n个位清0,标记已使用
c.cache &^= mask
// 这连续n个位清0,重置
c.scav &^= mask
// 起始地址,被清理的页数
return c.base + uintptr(i*pageSize), uintptr(scav) * pageSize
}

// 清空pageCache
func (c *pageCache) flush(p *pageAlloc) {
// 空函数(staticlockranking默认为false)
assertLockHeld(p.mheapLock)

// 为空不处理
if c.empty() {
return
}
// 索引
ci := chunkIndex(c.base)
// offset
pi := chunkPageIndex(c.base)

// 遍历bitmap,比对64个页
for i := uint(0); i < 64; i++ {
// 被使用
if c.cache&(1<<i) != 0 {
// 该位置为0
p.chunkOf(ci).free1(pi + i)

// 更新指定chunk元信息,如inUse、gen、scavChunkFlags等
p.scav.index.free(ci, pi+i, 1)
}
// 被标记为已清理
if c.scav&(1<<i) != 0 {
// pallocData.scavenged更新,指定位置为1
p.chunkOf(ci).scavenged.setRange(pi+i, 1)
}
}

// 如果释放的页地址比searchAddr小
if b := (offAddr{c.base}); b.lessThan(p.searchAddr) {
p.searchAddr = b
}

// 统计chunks信息并更新summary
p.update(c.base, pageCachePages, false, false)
*c = pageCache{}
}

pageAlloc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
func (p *pageAlloc) init(mheapLock *mutex, sysStat *sysMemStat, test bool) {
// 如果第一个元素!=21
if levelLogPages[0] > logMaxPackedValue {
print("runtime: root level max pages = ", 1<<levelLogPages[0], "\n")
print("runtime: summary max pages = ", maxPackedValue, "\n")
throw("root level max pages doesn't fit in summary")
}
p.sysStat = sysStat

// p.inUse初始化(ranges切片容量为256B)
p.inUse.init(sysStat)

// 从系统申请内存(Reserved)初始化p.summary数组(16KB~64MB)
p.sysInit(test)

// 最大地址 = (2^48-1) + 0xffff800000000000
p.searchAddr = maxSearchAddr()

// mheap.lock
p.mheapLock = mheapLock

// scavenge.index初始化,但固定返回0
p.summaryMappedReady += p.scav.index.init(test, sysStat)

// 是否是测试调用
p.test = test
}

// pageAlloc通过summary扫描寻找足以容纳n个页的地址
func (p *pageAlloc) alloc(npages uintptr) (addr uintptr, scav uintptr) {
// 空函数(staticlockranking默认为false)
assertLockHeld(p.mheapLock)

// 搜索地址searchAddr越界
if chunkIndex(p.searchAddr.addr()) >= p.end {
return 0, 0
}

// =0xffff800000000000
searchAddr := minOffAddr

// (512-(p.searchAddr%4194304/8192)) >= n => 当前chunk的剩余量足够容纳n个页
if pallocChunkPages-chunkPageIndex(p.searchAddr.addr()) >= uint(npages) {

// 从地址到索引,i = (p.searchAddr-0xffff800000000000)/4194304
i := chunkIndex(p.searchAddr.addr())

// 1. 第4层取第i个chunk的pallocSum的max段
// 2. max >= n => 当前chunk足够容纳n个页
if max := p.summary[len(p.summary)-1][i].max(); max >= uint(npages) {
// func (b *pallocBits) find(npages uintptr, searchIdx uint) (uint, uint)
// 在一个chunk中寻找可用的连续n个页的起始索引(最多512个页,从searchAddr开始搜索)
j, searchIdx := p.chunkOf(i).find(npages, chunkPageIndex(p.searchAddr.addr()))

// 固定数值,表示寻找失败,抛出异常
if j == ^uint(0) {
print("runtime: max = ", max, ", npages = ", npages, "\n")
print("runtime: searchIdx = ", chunkPageIndex(p.searchAddr.addr()), ", p.searchAddr = ", hex(p.searchAddr.addr()), "\n")
throw("bad summary data")
}

// 成功

// 连续n页内存的起始地址 = i*4194304+0xffff800000000000 + j*8192
addr = chunkBase(i) + uintptr(j)*pageSize
// chunk第一个可用页的位置 = i*4194304+0xffff800000000000 + searchIdx*8192
searchAddr = offAddr{chunkBase(i) + uintptr(searchIdx)*pageSize}
goto Found
}
}

// 当前组内存空间不足以容纳n个页(可能超过4MB)

// func (p *pageAlloc) find(npages uintptr) (uintptr, offAddr)
// 从summary最顶层开始扫描,寻找足以容纳n个页的内存地址和不小于addr的最近可用地址
addr, searchAddr = p.find(npages)
// 失败了
if addr == 0 {
// 只有一个页也失败了,可能达到了最大内存边界
if npages == 1 {
// 设置为最大内存边界 = (2^48-1) + 0xffff800000000000
p.searchAddr = maxSearchAddr()
}
return 0, 0
}

Found:
// 更新并重新统计chunks,最后更新summary(分配)
scav = p.allocRange(addr, npages)

// 如果最近可用地址比searchAddr大
if p.searchAddr.lessThan(searchAddr) {
// 更新
p.searchAddr = searchAddr
}
return addr, scav
}

// 更新并重新统计chunks,最后更新summary(释放)
func (p *pageAlloc) free(base, npages uintptr) {
// 空函数(staticlockranking默认为false)
assertLockHeld(p.mheapLock)

// 如果释放的页地址比searchAddr小
if b := (offAddr{base}); b.lessThan(p.searchAddr) {
// 更新
p.searchAddr = b
}
// 最后一个字节的地址
limit := base + npages*pageSize - 1

// 只释放一个页
if npages == 1 {
// chunk索引
i := chunkIndex(base)
// 位于512位bitmap的位置
pi := chunkPageIndex(base)
// 该位置为0
p.chunkOf(i).free1(pi)
// 更新指定chunk元信息,如inUse、gen、scavChunkFlags等
p.scav.index.free(i, pi, 1)
} else {
// 释放多个页

// 起始索引、终止索引
sc, ec := chunkIndex(base), chunkIndex(limit)
// 位于512位bitmap的位置
si, ei := chunkPageIndex(base), chunkPageIndex(limit)

// 同一个chunk
if sc == ec {
// 将这个范围的bitmap清0
p.chunkOf(sc).free(si, ei+1-si)
// 更新指定chunk元信息,如inUse、gen、scavChunkFlags等
p.scav.index.free(sc, si, ei+1-si)
} else {
// 不同chunk

// 第一个chunk
// 将这个范围的bitmap清0
p.chunkOf(sc).free(si, pallocChunkPages-si)
// 更新指定chunk元信息,如inUse、gen、scavChunkFlags等
p.scav.index.free(sc, si, pallocChunkPages-si)
// 中间chunk
for c := sc + 1; c < ec; c++ {
// pallocBits清除所有位
p.chunkOf(c).freeAll()
// 更新指定chunk元信息,如inUse、gen、scavChunkFlags等
p.scav.index.free(c, 0, pallocChunkPages)
}
// 最后一个chunk
// 将这个范围的bitmap清0
p.chunkOf(ec).free(0, ei+1)
// 更新指定chunk元信息,如inUse、gen、scavChunkFlags等
p.scav.index.free(ec, 0, ei+1)
}
}
// 统计chunks信息并更新summary
p.update(base, npages, true, false)
}

// 分配物理内存,更新并重新统计chunks信息,最后更新summary
func (p *pageAlloc) grow(base, size uintptr) {
// 空函数(staticlockranking默认为false)
assertLockHeld(p.mheapLock)

// base+size按4MB的倍数向上取整
limit := alignUp(base+size, pallocChunkBytes)
// base按4MB的倍数向下取整
base = alignDown(base, pallocChunkBytes)

// func (p *pageAlloc) sysGrow(base, limit uintptr)
// 在指定的虚拟地址范围内分配/映射物理内存,让该地址变为可用(Reserved->Prepared->Ready)
p.sysGrow(base, limit)

// func (s *scavengeIndex) grow(base, limit uintptr, sysStat *sysMemStat) uintptr
// 更新minHeapIdx,分配/映射物理内存使地址变为可用(Reserved->Prepared->Ready)
p.summaryMappedReady += p.scav.index.grow(base, limit, p.sysStat)

// 第一次走到这里
firstGrowth := p.start == 0
// 地址转索引
start, end := chunkIndex(base), chunkIndex(limit)
// 如果是第一次走到这里,直接纪录,否则取最小值
// p.start = min(p.start, start)
if firstGrowth || start < p.start {
p.start = start
}
// end会一直更新取最大值
if end > p.end {
p.end = end
}
// base、limit索引纪录到inuse
p.inUse.add(makeAddrRange(base, limit))

// 如果base比searchAddr小
if b := (offAddr{base}); b.lessThan(p.searchAddr) {
p.searchAddr = b
}

// 遍历这部份chunk
for c := chunkIndex(base); c < chunkIndex(limit); c++ {
// 如果chunk为nil,创建chunk
if p.chunks[c.l1()] == nil {
// 第二维有8192个指针
const l2Size = unsafe.Sizeof(*p.chunks[0])
// 直接向系统申请8192*8B=64KB大小内存(Ready)
r := sysAlloc(l2Size, p.sysStat)
// 申请失败
if r == nil {
throw("pageAlloc: out of memory")
}
// 非测试
if !p.test {
if p.chunkHugePages {
// 按physHugePageSize大小对齐,并尝试转成huge page(只有linux有)
sysHugePage(r, l2Size)
} else {
// 不使用huge page
sysNoHugePage(r, l2Size)
}
}
// 纪录指针
*(*uintptr)(unsafe.Pointer(&p.chunks[c.l1()])) = uintptr(r)
}
// pallocData.scavenged更新,指定位置为1(0,512)
p.chunkOf(c).scavenged.setRange(0, pallocChunkPages)
}

// 统计chunks信息并更新summary
p.update(base, size/pageSize, true, false)
}

// 更新并重新统计chunks,最后更新summary(分配)
func (p *pageAlloc) allocRange(base, npages uintptr) uintptr {
// 空函数(staticlockranking默认为false)
assertLockHeld(p.mheapLock)

// 最后一个字节的地址
limit := base + npages*pageSize - 1
// chunk索引
sc, ec := chunkIndex(base), chunkIndex(limit)
// 位于512位bitmap的位置
si, ei := chunkPageIndex(base), chunkPageIndex(limit)

// 内存重用后,scavenged还设置为1,需要统计这部份内存
scav := uint(0)
// 同一个chunk
if sc == ec {
chunk := p.chunkOf(sc)
// 计算区间内1的数量
scav += chunk.scavenged.popcntRange(si, ei+1-si)
// 区间内的pallocBits设置为1、scavenged重置为0
chunk.allocRange(si, ei+1-si)
// 更新指定chunk元信息,如inUse、gen、scavChunkFlags等
p.scav.index.alloc(sc, ei+1-si)
} else {
// 多个chunk
// 第一个chunk
chunk := p.chunkOf(sc)
// 计算区间内1的数量
scav += chunk.scavenged.popcntRange(si, pallocChunkPages-si)
// 区间内的pallocBits设置为1、scavenged重置为0
chunk.allocRange(si, pallocChunkPages-si)
// 更新指定chunk元信息,如inUse、gen、scavChunkFlags等
p.scav.index.alloc(sc, pallocChunkPages-si)
// 中间chunk
for c := sc + 1; c < ec; c++ {
chunk := p.chunkOf(c)
// 计算区间内1的数量
scav += chunk.scavenged.popcntRange(0, pallocChunkPages)
// 64位全设置为1
chunk.allocAll()
// 更新指定chunk元信息,如inUse、gen、scavChunkFlags等
p.scav.index.alloc(c, pallocChunkPages)
}
// 最后一个chunk
chunk = p.chunkOf(ec)
// 计算区间内1的数量
scav += chunk.scavenged.popcntRange(0, ei+1)
// 区间内的pallocBits设置为1、scavenged重置为0
chunk.allocRange(0, ei+1)
// 更新指定chunk元信息,如inUse、gen、scavChunkFlags等
p.scav.index.alloc(ec, ei+1)
}
// 统计chunks信息并更新summary
p.update(base, npages, true, true)
return uintptr(scav) * pageSize
}

// 统计chunks信息并更新summary
func (p *pageAlloc) update(base, npages uintptr, contig, alloc bool) {
// 空函数(staticlockranking默认为false)
assertLockHeld(p.mheapLock)

// 指向最后一个字节
limit := base + npages*pageSize - 1
// 起始idx、终止idx(指针转idx)
sc, ec := chunkIndex(base), chunkIndex(limit)

// 统计chunks信息并更新到summary

// 同一个chunk,4MB内存内
if sc == ec {
// x = p.summary[4][sc] => pallocSum
x := p.summary[len(p.summary)-1][sc]
// 统计前导连续空闲页数,最大连续空闲页数,末尾连续空闲页数
// chunk => p.chunks[sc>>13][sc&(2^13-1)] => pallocData
y := p.chunkOf(sc).summarize()
// 不需要更新
if x == y {
return
}
// 以实际统计为准
p.summary[len(p.summary)-1][sc] = y
} else if contig {
// 不同一个chunk、连续

// = p.summary[4]
summary := p.summary[len(p.summary)-1]

// 更新第一个chunk:统计前导连续空闲页数,最大连续空闲页数,末尾连续空闲页数
// chunk => p.chunks[sc>>13][sc&(2^13-1)] => pallocData
summary[sc] = p.chunkOf(sc).summarize()

// 中间chunk,不包含sc和ec
whole := p.summary[len(p.summary)-1][sc+1 : ec]
if alloc {
// 分配内存,整个chunk都被使用
// 清0
clear(whole)
} else {
// 刚从系统申请的内存或被GC清扫后的内存,重置为默认值
for i := range whole {
// = |0|512|512|512|
whole[i] = freeChunkSum
}
}

// 更新最后一个chunk:统计前导连续空闲页数,最大连续空闲页数,末尾连续空闲页数
summary[ec] = p.chunkOf(ec).summarize()
} else {
// 不同一个chunk、不连续

// = p.summary[4]
summary := p.summary[len(p.summary)-1]
// 遍历所有chunk
for c := sc; c <= ec; c++ {
// 更新summary:统计前导连续空闲页数,最大连续空闲页数,末尾连续空闲页数
summary[c] = p.chunkOf(c).summarize()
}
}

// 到这里已经算出每个chunk的sum信息,也就是summary最后一层已经计算完,需要往前更新

// 表示第l层需要更新
changed := true
// 从后向前遍历剩下的summary
// p.summary[3] ... p.summary[0]
for l := len(p.summary) - 2; l >= 0 && changed; l-- {
// 合并后的sum没有变化,则提前退出循环
changed = false

// 一般是3个bit,也就是除以8
logEntriesPerBlock := levelBits[l+1]
// 一个sum代表的页数量,第4层一个sum代表512页,每上一层数量*=8,这里取对数值
logMaxPages := levelLogPages[l+1]

// |16|14| 3| 3| 3| 3|22|
// | |l0|l1|l2|l3|l4| |
// 地址丢弃低22位,然后每上一层,丢弃3位
lo, hi := addrsToSummaryRange(l, base, limit+1)

// 下一层的每8个sum汇总成一个sum
for i := lo; i < hi; i++ {
// 第i个block,每个block有logEntriesPerBlock个entry
children := p.summary[l+1][i<<logEntriesPerBlock : (i+1)<<logEntriesPerBlock]
// 每8个sum汇总成一个sum
sum := mergeSummaries(children, logMaxPages)
old := p.summary[l][i]
// 相同则不更新
if old != sum {
// 不同,还要继续往上一层合并
changed = true
p.summary[l][i] = sum
}
}
}
}

// 每8个sum汇总成一个sum
func mergeSummaries(sums []pallocSum, logMaxPagesPerSum uint) pallocSum {
// 解析第1个sum
start, most, end := sums[0].unpack()

// 遍历剩下的sum
for i := 1; i < len(sums); i++ {
// 解析第i个sum
si, mi, ei := sums[i].unpack()

// 前导连续空闲页数
if start == uint(i)<<logMaxPagesPerSum {
// 如果start是512(更高层*8),代表前一个chunk没有被使用
start += si
}

// 最大连续空闲页数
most = max(most, end+si, mi)

// 末尾连续空闲页数
if ei == 1<<logMaxPagesPerSum {
// 如果end是512(更高层*8),代表当前chunk没有被使用
end += 1 << logMaxPagesPerSum
} else {
end = ei
}
}
return packPallocSum(start, most, end)
}

// 找出不小于addr的最近可用地址(一般情况下返回传入的参数)
func (p *pageAlloc) findMappedAddr(addr offAddr) offAddr {
// 空函数(staticlockranking默认为false)
assertLockHeld(p.mheapLock)

// 转换为anera索引
ai := arenaIndex(addr.addr())
// 测试中 or heapArena尚未分配或被释放
if p.test || mheap_.arenas[ai.l1()] == nil || mheap_.arenas[ai.l1()][ai.l2()] == nil {
// 在inUse.ranges内找到不小于addr的地址
vAddr, ok := p.inUse.findAddrGreaterEqual(addr.addr())
if ok {
// 找到了
return offAddr{vAddr}
} else {
// 没找到
return maxOffAddr
}
}
// 一般情况下返回传入的参数
return addr
}

// 从summary最顶层开始扫描,寻找足以容纳n个页的内存地址和不小于addr的最近可用地址
func (p *pageAlloc) find(npages uintptr) (uintptr, offAddr) {
// 空函数(staticlockranking默认为false)
assertLockHeld(p.mheapLock)

// 可以认为是基地址的索引
i := 0

// base和bound基本覆盖了整个48位地址空间
firstFree := struct {
base, bound offAddr
}{
base: minOffAddr, // 基地址 => 0xffff800000000000
bound: maxOffAddr, // 终止地址 => (2^48-1) + 0xffff800000000000
}

// 该函数用于检查地址是否合法,合法则更新到firstFree变量,不合法则抛出异常
foundFree := func(addr offAddr, size uintptr) {
// 边界检测
if firstFree.base.lessEqual(addr) && addr.add(size-1).lessEqual(firstFree.bound) {
// 合法,纪录
firstFree.base = addr
firstFree.bound = addr.add(size - 1)
} else if !(addr.add(size-1).lessThan(firstFree.base) || firstFree.bound.lessThan(addr)) {
// 异常
print("runtime: addr = ", hex(addr.addr()), ", size = ", size, "\n")
print("runtime: base = ", hex(firstFree.base.addr()), ", bound = ", hex(firstFree.bound.addr()), "\n")
throw("range partially overlaps")
}
}

// 下面两个参数debug用
// 上一个sum-初始值表示全部已使用
lastSum := packPallocSum(0, 0, 0)
// 上一个sum索引
lastSumIdx := -1

nextLevel:
// 遍历summary,从最顶层到最底层
for l := 0; l < len(p.summary); l++ {
// 每层需要扫描的的entry数量
// 第0层全部16K个块都要扫描,每下一层只需要扫描8个块
entriesPerBlock := 1 << levelBits[l]
// 一个sum代表的页数量,第4层一个sum代表512页(4MB),每上一层数量*=8,第0层代表2M个页(4GB)
logMaxPages := levelLogPages[l]

// 地址索引,第0层乘于16K,但因为初始为0,所以结果还是0,而后每下一层,乘于8
// i在后面还会累加偏移量j
i <<= levelBits[l]

// 从i位置开始扫描
// 第0层全部16K个块都要扫描,每下一层只需要扫描8个块
entries := p.summary[l][i : i+entriesPerBlock]

// 偏移量
j0 := 0

// 1. 先将searchAddr转换为相对地址,再除于每层entry数量,获得数组索引
// 2. searchIdx丢弃低位 => 块的起始地址,判断searchIdx是不是在i指向的块内
if searchIdx := offAddrToLevelIndex(l, p.searchAddr); searchIdx&^(entriesPerBlock-1) == i {
// 取searchIdx低位 => 第i块内的偏移量
j0 = searchIdx & (entriesPerBlock - 1)
}

// 可用页的起始位置,累计总页数
var base, size uint
// 从0或j0的位置开始扫描整个entries数组
for j := j0; j < len(entries); j++ {
// sum
sum := entries[j]
// 全部已使用
if sum == 0 {
size = 0
continue
}

// 部份已使用

// 1. 索引转绝对地址
// 2. 检查地址是否合法,合法则更新到firstFree,不合法则抛出异常
foundFree(levelIndexToOffAddr(l, i+j), (uintptr(1)<<logMaxPages)*pageSize)

// start部份-前导连续空闲页数
s := sum.start()
// 空间足够
if size+s >= uint(npages) {
// 调整起始位置
if size == 0 {
base = uint(j) << logMaxPages
}
size += s
break
}

// 空间不足

// max部份-最大连续空闲页数,可以容纳n个页
if sum.max() >= uint(npages) {
// 进入块内部扫描

// 偏移量
i += j
// 下面两个参数debug用
// 上一层的偏移量
lastSumIdx = i
// 上一层的sum
lastSum = sum
// 向下一层寻找
continue nextLevel
}

// 如果最大连续空闲页数不足以容纳n个页

// 未开始累计 or 当前块部份使用
if size == 0 || s < 1<<logMaxPages {
// end-末尾连续空闲页数
size = sum.end()
// 更新base到正确的起始位置
base = uint(j+1)<<logMaxPages - size
continue
}

// 已开始累计 and 当前块全部可用

// 直接累加
size += 1 << logMaxPages
}

// 到这里,已经找到足够的n个页,或当前层的x个块已经扫描完了

// 足以容纳n个页
if size >= uint(npages) {
// 1. 索引转绝对地址
// 2. 检查地址是否合法,合法则更新到firstFree,不合法则抛出异常
addr := levelIndexToOffAddr(l, i).add(uintptr(base) * pageSize).addr()
// addr, 找出不小于addr的最近可用地址(一般情况下返回传入的参数)
return addr, p.findMappedAddr(firstFree.base)
}

// 不足以容纳n个页

// 第0层,说明整个地址空间都扫描过了,内存不够用
if l == 0 {
return 0, maxSearchAddr()
}

// 其他层不应该出现这种情况,异常,打印信息
print("runtime: summary[", l-1, "][", lastSumIdx, "] = ", lastSum.start(), ", ", lastSum.max(), ", ", lastSum.end(), "\n")
print("runtime: level = ", l, ", npages = ", npages, ", j0 = ", j0, "\n")
print("runtime: p.searchAddr = ", hex(p.searchAddr.addr()), ", i = ", i, "\n")
print("runtime: levelShift[level] = ", levelShift[l], ", levelBits[level] = ", levelBits[l], "\n")
for j := 0; j < len(entries); j++ {
sum := entries[j]
print("runtime: summary[", l, "][", i+j, "] = (", sum.start(), ", ", sum.max(), ", ", sum.end(), ")\n")
}
throw("bad summary data")
}

// 兜底代码?按道理怎么也不会走到这里才对!!!

// chunk索引
ci := chunkIdx(i)
// 在一个chunk中寻找可用的连续n个页的起始索引(最多512个页,从基地址开始搜索)
j, searchIdx := p.chunkOf(ci).find(npages, 0)

// 固定数值,表示寻找失败,抛出异常
if j == ^uint(0) {
sum := p.summary[len(p.summary)-1][i]
print("runtime: summary[", len(p.summary)-1, "][", i, "] = (", sum.start(), ", ", sum.max(), ", ", sum.end(), ")\n")
print("runtime: npages = ", npages, "\n")
throw("bad summary data")
}

// 索引转地址
addr := chunkBase(ci) + uintptr(j)*pageSize

// 索引转地址
searchAddr := chunkBase(ci) + uintptr(searchIdx)*pageSize
// 检查地址是否合法,合法则更新到firstFree,不合法则抛出异常
foundFree(offAddr{searchAddr}, chunkBase(ci+1)-searchAddr)
// addr, 找出不小于addr的最近可用地址(一般情况下返回传入的参数)
return addr, p.findMappedAddr(firstFree.base)
}

// 通过summary查找,找到一个最少包含一个可用页的块(共64个页信息)
func (p *pageAlloc) allocToCache() pageCache {
// 空函数(staticlockranking默认为false)
assertLockHeld(p.mheapLock)

// 地址转索引,越界
if chunkIndex(p.searchAddr.addr()) >= p.end {
return pageCache{}
}
// 未越界
c := pageCache{}
// 起始索引
ci := chunkIndex(p.searchAddr.addr())

var chunk *pallocData

if p.summary[len(p.summary)-1][ci] != 0 {
// 最底层的chunk的pallocSum有剩余的页

// chunk
chunk = p.chunkOf(ci)
// func (b *pallocBits) find(npages uintptr, searchIdx uint) (uint, uint)
// 在一个chunk中寻找可用的连续n个页的起始索引(最多512个页,从searchAddr开始搜索)
j, _ := chunk.find(1, chunkPageIndex(p.searchAddr.addr()))

// 固定数值,表示寻找失败,抛出异常
if j == ^uint(0) {
throw("bad summary data")
}
// 找到了
c = pageCache{
// 对象地址 = 基地址 + [0,512)*8192
base: chunkBase(ci) + alignDown(uintptr(j), 64)*pageSize,
// 64位取反,这里1表示页是可用的
cache: ^chunk.pages64(j),
// 直接使用64位
scav: chunk.scavenged.block64(j),
}
} else {
// 最底层的chunk的pallocSum没有剩余的页 => 为0

// func (p *pageAlloc) find(npages uintptr) (uintptr, offAddr)
// 从summary最顶层开始扫描,寻找足以容纳1个页的内存地址和最近可用地址
addr, _ := p.find(1)
// 失败了
if addr == 0 {
// 内存不足
p.searchAddr = maxSearchAddr()
return pageCache{}
}
// 成功
ci = chunkIndex(addr)
chunk = p.chunkOf(ci)
c = pageCache{
// 对象地址
base: alignDown(addr, 64*pageSize),
// 64位取反,这里1表示页是可用的
cache: ^chunk.pages64(chunkPageIndex(addr)),
// 直接使用64位
scav: chunk.scavenged.block64(chunkPageIndex(addr)),
}
}

// 这部份数据被缓存,原来的chunk需要更新为已使用
// 索引
cpi := chunkPageIndex(c.base)
// 用cache的bitmap合并原chunk数据,这样原64位全为1
chunk.allocPages64(cpi, c.cache)
// 如果一个页被使用,后被回收,scavenged的状态需要重置为0
chunk.scavenged.clearBlock64(cpi, c.cache&c.scav)

// 统计chunks信息并更新summary
p.update(c.base, pageCachePages, false, true)

// func (s *scavengeIndex) alloc(ci chunkIdx, npages uint)
// 更新指定chunk元信息,如inUse、gen、scavChunkFlags等。防止scavenger错误回收已分配的页
p.scav.index.alloc(ci, uint(sys.OnesCount64(c.cache)))

// 搜索地址往后移动63个页 => 8192*63
p.searchAddr = offAddr{c.base + pageSize*(pageCachePages-1)}
return c
}

// 在指定的虚拟地址范围内分配/映射物理内存,让该地址变为可用(Reserved->Prepared->Ready)
func (p *pageAlloc) sysGrow(base, limit uintptr) {
// 非4MB的倍数,异常
if base%pallocChunkBytes != 0 || limit%pallocChunkBytes != 0 {
print("runtime: base = ", hex(base), ", limit = ", hex(limit), "\n")
throw("sysGrow bounds not aligned to pallocChunkBytes")
}

// 根据地址找到sum的索引边界
addrRangeToSummaryRange := func(level int, r addrRange) (int, int) {
// |16|14| 3| 3| 3| 3|22|
// | |l0|l1|l2|l3|l4| |
// 地址丢弃低22位,然后每上一层,丢弃3位,l0时是一个14位的数,l4时是一个26位数
sumIdxBase, sumIdxLimit := addrsToSummaryRange(level, r.base.addr(), r.limit.addr())
// 按指定倍数,base向下取整,limit向上取整,l0时倍数为2^14
return blockAlignSummaryRange(level, sumIdxBase, sumIdxLimit)
}

// 计算出当前层的summary的起始、终止索引
summaryRangeToSumAddrRange := func(level, sumIdxBase, sumIdxLimit int) addrRange {
// base*8,按physPageSize向下取整
baseOffset := alignDown(uintptr(sumIdxBase)*pallocSumBytes, physPageSize)
// limit*8,按physPageSize向上取整
limitOffset := alignUp(uintptr(sumIdxLimit)*pallocSumBytes, physPageSize)
// 当前层的第一个sum
base := unsafe.Pointer(&p.summary[level][0])

return addrRange{
offAddr{uintptr(add(base, baseOffset))}, // 当前层的base索引
offAddr{uintptr(add(base, limitOffset))}, // 当前层的limit索引
}
}

// 根据地址计算出其在summary的起始、终止索引
addrRangeToSumAddrRange := func(level int, r addrRange) addrRange {
// 根据地址找到sum的索引边界
sumIdxBase, sumIdxLimit := addrRangeToSummaryRange(level, r)
// 计算出当前层的summary的起始、终止索引
return summaryRangeToSumAddrRange(level, sumIdxBase, sumIdxLimit)
}

// 用二分法在inUse.ranges中寻找范围包含base的addrRange的索引
inUseIndex := p.inUse.findSucc(base)

// 遍历summary
for l := range p.summary {
// 根据地址找到sum的索引边界
needIdxBase, needIdxLimit := addrRangeToSummaryRange(l, makeAddrRange(base, limit))

// 防止越界
if needIdxLimit > len(p.summary[l]) {
p.summary[l] = p.summary[l][:needIdxLimit]
}

// 计算出当前层的summary的起始、终止索引
need := summaryRangeToSumAddrRange(l, needIdxBase, needIdxLimit)

// 由于是二分法,这个索引可能越界
if inUseIndex > 0 {
// 1. 根据地址计算出其在summary的起始、终止索引
// 2. need根据range的地址范围调整base和limit
// 注意:inUseIndex索引下的地址范围可能会包含addr
need = need.subtract(addrRangeToSumAddrRange(l, p.inUse.ranges[inUseIndex-1]))
}
if inUseIndex < len(p.inUse.ranges) {
// 1. 根据地址计算出其在summary的起始、终止索引
// 2. need根据range的地址范围调整base和limit
// 注意:inUseIndex索引下的地址范围不可能会包含addr
need = need.subtract(addrRangeToSumAddrRange(l, p.inUse.ranges[inUseIndex]))
}

// b.base < a.base < a.limit < b.limit
// 如果need的地址范围被包含在range的地址范围内时,base和limit设置为0
if need.size() == 0 {
// 前往下一层
continue
}

// 走到这里,need和range的地址范围有交叉,需要更新base或者limit
// a.base < b.limit < a.limit => a.base=b.limit(此时b.base的位置随意)
// a.base < b.base < a.limit => a.limit=b.base(此时b.limit的位置随意)

// 使用sysMap直接映射,内存状态从Reserved改为Prepared
sysMap(unsafe.Pointer(need.base.addr()), need.size(), p.sysStat)
// 内存状态从Prepared改为Ready
sysUsed(unsafe.Pointer(need.base.addr()), need.size(), need.size())
// 这部份内存是Ready状态,累计到summaryMappedReady
p.summaryMappedReady += need.size()
}

// func (s *scavengeIndex) sysGrow(base, limit uintptr, sysStat *sysMemStat) uintptr
// 在指定的虚拟地址范围内分配/映射物理内存,让该地址变为可用(Reserved->Prepared->Ready)
p.summaryMappedReady += p.scav.index.sysGrow(base, limit, p.sysStat)
}


// chunk元素按physHugePageSize大小对齐,并尝试转成huge page(只有linux有)
func (p *pageAlloc) enableChunkHugePages() {
// 加锁
lock(&mheap_.lock)
// 已修改
if p.chunkHugePages {
// 解锁返回
unlock(&mheap_.lock)
return
}
// 设置标志
p.chunkHugePages = true

var inUse addrRanges
inUse.sysStat = p.sysStat
// 复制p.inUse到inUse变量(如果inUse容量较小,还会进行扩容)
p.inUse.cloneInto(&inUse)
unlock(&mheap_.lock)

// 遍历所有地址
for _, r := range p.inUse.ranges {
for i := chunkIndex(r.base.addr()).l1(); i < chunkIndex(r.limit.addr()-1).l1(); i++ {
// 按physHugePageSize大小对齐,并尝试转成huge page(只有linux有)
sysHugePage(unsafe.Pointer(p.chunks[i]), unsafe.Sizeof(*p.chunks[0]))
}
}
}

pallocBits

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
// 在一个chunk中寻找可用的连续n个页的起始索引(最多512个页)
func (b *pallocBits) find(npages uintptr, searchIdx uint) (uint, uint) {
if npages == 1 {
// 1个page
// 寻找可用的1个页的起始索引(最多1个页)
addr := b.find1(searchIdx)
return addr, addr
} else if npages <= 64 {
// <=64个page
// 寻找可用的连续n个页的起始索引(最多64个页)
return b.findSmallN(npages, searchIdx)
}
// >64个page
// 寻找可用的连续n个页的起始索引(最多512个页)
return b.findLargeN(npages, searchIdx)
}

// 寻找可用的1个页的起始索引(最多1个页)
func (b *pallocBits) find1(searchIdx uint) uint {
// 检查是否为nil
_ = b[0]
// i = chunk二级索引/64 => 定位是第几个uint64
for i := searchIdx / 64; i < uint(len(b)); i++ {
// 64个位
x := b[i]
// 取反,如0000 0111 -> 1111 1000
if ^x == 0 {
// 如果x的64个位全为1,全部已分配,跳过
continue
}
// 定位页起始索引 => i->基索引,64位二进制数的尾部的0的个数 -> 空闲位
return i*64 + uint(sys.TrailingZeros64(^x))
}
// 全部已分配
return ^uint(0)
}

// 寻找可用的连续n个页的起始索引(最多64个页)
func (b *pallocBits) findSmallN(npages uintptr, searchIdx uint) (uint, uint) {
// 高位连续0个数,chunk第一个可用页的位置
end, newSearchIdx := uint(0), ^uint(0)
// i = chunk二级索引/64 => 范围:[0,8),定位是第几个uint64
for i := searchIdx / 64; i < uint(len(b)); i++ {
// 64个位
bi := b[i]
// 取反,如0000 0111 -> 1111 1000
if ^bi == 0 {
// 说明bi的64个位全为1,全部已分配,跳过
end = 0
continue
}

// 有至少一个页可用

// 第一次走到这里
if newSearchIdx == ^uint(0) {
// chunk第一个可用页的位置(后面判断可能发现不连续/不够用)
newSearchIdx = i*64 + uint(sys.TrailingZeros64(^bi))
}
// 低位连续0个数
start := uint(sys.TrailingZeros64(bi))

// 判断连续空闲页数是否满足需求,举例
// 上一个64位bitmap => 0001 1111 ... => end = 3
// 当前的64位bitmap => 1111 0000 ... => start = 60
if end+start >= uint(npages) {
// 连续可用n页的起始位置,chunk第一个可用页的位置
return i*64 - end, newSearchIdx
}
// 找出连续n个1的起始位置(64个位),找不到返回64
j := findBitRange64(^bi, uint(npages))
// 有足够空间容纳连续npages
if j < 64 {
// 连续可用n页的起始位置,chunk第一个可用页的位置
return i*64 + j, newSearchIdx
}
// 没有足够空间容纳连续npages

// 高位连续0个数
end = uint(sys.LeadingZeros64(bi))
}
// 没找到
return ^uint(0), newSearchIdx
}

// 寻找可用的连续n个页的起始索引(最多512个页)
func (b *pallocBits) findLargeN(npages uintptr, searchIdx uint) (uint, uint) {
// 连续可用n页的起始位置,连续1的数量,chunk第一个可用页的位置
start, size, newSearchIdx := ^uint(0), uint(0), ^uint(0)
// i = chunk二级索引/64 => 定位是第几个uint64
for i := searchIdx / 64; i < uint(len(b)); i++ {
// 64个位
x := b[i]
// 取反,如0000 0111 -> 1111 1000
if x == ^uint64(0) {
// 如果x的64个位全为1,全部已分配,跳过
size = 0
continue
}

// 有至少一个页可用

// 第一次走到这里
if newSearchIdx == ^uint(0) {
// chunk第一个可用页的位置(后面判断可能发现不连续/不够用)
newSearchIdx = i*64 + uint(sys.TrailingZeros64(^x))
}

// 未改动/被重置
if size == 0 {
// 高位连续0个数
size = uint(sys.LeadingZeros64(x))
// 连续可用n页的起始位置
start = i*64 + 64 - size
continue
}

// 到这里,起始位置已经确定,看能否找到足够的空间

// 低位连续0个数
s := uint(sys.TrailingZeros64(x))

// 判断连续空闲页数是否满足需求,举例
// 上一个64位bitmap => 0001 1111 ... => end = 3
// 当前的64位bitmap => 1111 0000 ... => start = 60
if s+size >= uint(npages) {
// 连续可用n页的起始位置,chunk第一个可用页的位置
return start, newSearchIdx
}

// 到这里,64位扫描完了,还是不够用,继续扫描下一个64位

// s!=64,64位中只有一部分可用,重置
if s < 64 {
// 高位连续0个数
size = uint(sys.LeadingZeros64(x))
// 连续可用n页的起始位置
start = i*64 + 64 - size
continue
}
// 64位全部可用,直接累加
size += 64
}
// 不够
if size < uint(npages) {
return ^uint(0), newSearchIdx
}
// 够了
return start, newSearchIdx
}

// 找出连续n个1的起始位置(64个位),找不到返回64
func findBitRange64(c uint64, n uint) uint {
// 这个方法的神奇之处就是能够快速找出连续n个1的起始位置(64个位)

// 剩余位,每次循环后减k,k的范围:1,2,4,8,...
p := n - 1
// 丢弃位的数量,每次循环后翻倍,取值范围:1,2,4,8...
k := uint(1)

// 一共会丢弃n-1个位
for p > 0 {
// 要保证k<p
if p <= k {
c &= c >> (p & 63)
break
}

// 丢弃k个位,如果有x个连续的1,经过这个操作,x-=k
c &= c >> (k & 63)
// 还没操作完,但c已经为0了,表示连续的1数量不足以容纳n
if c == 0 {
return 64
}
p -= k
// 丢弃位的数量翻倍
k *= 2
}

// 此时c内第一个1就是连续n个页的起始位置
return uint(sys.TrailingZeros64(c))
}


// 统计前导连续空闲页数,最大连续空闲页数,末尾连续空闲页数
func (b *pallocBits) summarize() pallocSum {
// 前导连续空闲页数,最大连续空闲页数,末尾连续空闲页数
var start, most, cur uint
// 特殊值
const notSetYet = ^uint(0)
start = notSetYet
// 8个uint64,bitmap中1为已使用,0为未使用
for i := 0; i < len(b); i++ {
// 第i个uint64
x := b[i]

if x == 0 {
// 64个页均未使用
cur += 64
continue
}

// 64个页中有已使用的页

// 二进制数x尾部0个数
t := uint(sys.TrailingZeros64(x))
// 二进制数x头部0个数
l := uint(sys.LeadingZeros64(x))

// 累计cur+t个页(未使用)
cur += t

// start为初始值,需要更新
if start == notSetYet {
// 出现已使用的页,纪录该页的索引
start = cur
}
// 连续的未使用页数最大值
most = max(most, cur)
// l累计到下一个uint64
cur = l
}

// 512个页都未被使用
if start == notSetYet {
// n=512
const n = uint(64 * len(b))
// |0|512|512|512|
return packPallocSum(n, n, n)
}

// 有使用的情况

// 得出最后的most
most = max(most, cur)

// 连续未分配页数量最大值超过62,就算 1xxxxx1 内部有0也不可能超过这个数
if most >= 64-2 {
// |0|cur|most|start|
return packPallocSum(start, most, cur)
}

// 到这里,所有的uint64都不可能为0, 1xxxxx1 内部搜索连续0的数量,如果比most大,则更新most

outer:
// 重新扫描
for i := 0; i < len(b); i++ {
x := b[i]

// 示例:000000 1xxxxx1 000000
// 需要确保 1xxxxx1 内部没有连续0

// 丢弃所有低位的0
x >>= sys.TrailingZeros64(x) & 63
if x&(x+1) == 0 {
// 内部没有0
continue
}

// 内部有0

// 剩余量=连续0最大值
p := most
// 1的数量
k := uint(1)
for {
for p > 0 {
if p <= k {
// 用高位的p个1向右移位覆盖内部的0
x |= x >> (p & 63)
if x&(x+1) == 0 {
// 内部0数量<most,扫描下一组
continue outer
}
// 内部0数量超过most
break
}
// 用高位的k个1向右移位覆盖内部的0
x |= x >> (k & 63)
if x&(x+1) == 0 {
// 内部0数量<most,扫描下一组
continue outer
}
// 剩余量
p -= k
// k翻倍
k *= 2
}

// 内部0数量超过most

j := uint(sys.TrailingZeros64(^x)) // 尾部1的数量
x >>= j & 63 // 丢弃尾部1
j = uint(sys.TrailingZeros64(x)) // 尾部0的数量
x >>= j & 63 // 丢弃尾部0
most += j // most+j就是 1xxxxx1 内部连续0的其中一个值
if x&(x+1) == 0 { // 没有其他0了,扫描下一组
continue outer
}

// 比如 1xxxxx1 内部连续0的数量有6、5两种情况,而most为4,只计算到了5,6没有计算到
p = j // 剩余量从j开始
}
}

// |0|cur|most|start|
return packPallocSum(start, most, cur)
}

GC助攻

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
// 降低g的Assist额度,如果额度用光了,则g需要协助GC标记(g会被挂起)
func deductAssistCredit(size uintptr) {
// g
assistG := getg()
// 重复,避免当前g是调度的g0
if assistG.m.curg != nil {
assistG = assistG.m.curg
}
// 额度-=size
assistG.gcAssistBytes -= int64(size)

// 额度用光了
if assistG.gcAssistBytes < 0 {
// 协助GC标记,全局额度bgScanCredit有数据则偷额度,没有则协助标记还债,还不清则挂起等待
gcAssistAlloc(assistG)
}
}

// 分配内存时,如果sweeper还在清扫中且分配速度比清扫速度快,则协助sweeper清扫
func deductSweepCredit(spanBytes uintptr, callerSweepPages uintptr) {
// 清扫完毕
if mheap_.sweepPagesPerByte == 0 {
return
}

// 还在清扫中

retry:
// pagesSwept快照-已完成清扫的页数量(标记终止阶段时纪录)
sweptBasis := mheap_.pagesSweptBasis.Load()
// heap存活字节数(标记终止阶段纪录或分配内存时调整)
live := gcController.heapLive.Load()
// heapLive快照(标记终止阶段时纪录)
liveBasis := mheap_.sweepHeapLiveBasis
// mspan字节数
newHeapLive := spanBytes
// 一般情况下heapLive>=sweepHeapLiveBasis
if liveBasis < live {
// 累加内存分配时增加的字节数
newHeapLive += uintptr(live - liveBasis)
}
// 目标页数=每分配1字节需要清扫的页数*总字节数-n个页
pagesTarget := int64(mheap_.sweepPagesPerByte*float64(newHeapLive)) - int64(callerSweepPages)
// 目标页数 > sweeper清扫的页总数 => 短时间内分配大量内存
for pagesTarget > int64(mheap_.pagesSwept.Load()-sweptBasis) {
// 不断的逐个清扫mspan
if sweepone() == ^uintptr(0) {
// sweeper清扫完了
// 重置
mheap_.sweepPagesPerByte = 0
// 退出
break
}

// sweeper还没清扫完

// GCPercent/MemoryLimit有变动
if mheap_.pagesSweptBasis.Load() != sweptBasis {
// 重试
goto retry
}
}
}

// 协助GC标记,全局额度bgScanCredit有数据则偷额度,没有则协助标记还债,还不清则挂起等待
func gcAssistAlloc(gp *g) {
// g0不可被抢占
if getg() == gp.m.g0 {
return
}
// 被抢占
if mp := getg().m; mp.locks > 0 || mp.preemptoff != "" {
return
}

// 测试,忽略
if gp := getg(); gp.syncGroup != nil {
sg := gp.syncGroup
gp.syncGroup = nil
defer func() {
gp.syncGroup = sg
}()
}

// 是否需要协助GC标记
enteredMarkAssistForTracing := false
retry:
// CPU限制GC使用
if gcCPULimiter.limiting() {
// 如果先前已经协助GC标记
if enteredMarkAssistForTracing {
// 不协助GC标记,降低GC对CPU的使用(trace相关的代码我删了)
gp.inMarkAssist = false
}
return
}

// 不限制

// 转换参数
assistWorkPerByte := gcController.assistWorkPerByte.Load()
assistBytesPerWork := gcController.assistBytesPerWork.Load()
// 额度/欠债,此时gcAssistBytes为负数
debtBytes := -gp.gcAssistBytes
// 任务量
scanWork := int64(assistWorkPerByte * float64(debtBytes))
// 最低64K
if scanWork < gcOverAssistWork {
// 设置为64K
scanWork = gcOverAssistWork
// 总欠债
debtBytes = int64(assistBytesPerWork * float64(scanWork))
}

// 全局扫描额度
bgScanCredit := gcController.bgScanCredit.Load()
stolen := int64(0)
// 全局额度还有一定数量
if bgScanCredit > 0 {
if bgScanCredit < scanWork {
// 剩余额度不足
// 剩余的全部拿走
stolen = bgScanCredit
// 调整欠债
gp.gcAssistBytes += 1 + int64(assistBytesPerWork*float64(stolen))
} else {
// 剩余额度充足
// 一次解决欠债
stolen = scanWork
gp.gcAssistBytes += debtBytes
}
// 经过上面的操作,gcAssistBytes可能为正

// 全局扫描额度更新,并发情况下,可能为负数
gcController.bgScanCredit.Add(-stolen)

scanWork -= stolen

// 偷走的额度跟欠债一致
if scanWork == 0 {
// 如果先前已经协助GC标记
if enteredMarkAssistForTracing {
// 不协助GC标记,降低GC对CPU的使用(trace相关的代码我删了)
gp.inMarkAssist = false
}
return
}

// 还有欠债
}

// 没有额度 或 额度不足以偿还

// 有欠债,且没有协助GC标记过(洗盘子还债去吧)
if !enteredMarkAssistForTracing {
// 有欠债,需要协助GC标记(trace相关的代码我删了)
gp.inMarkAssist = true
// 设置为true,进入协助标记流程
enteredMarkAssistForTracing = true
}

// 切换到g0运行
systemstack(func() {
// 协助GC标记
gcAssistAlloc1(gp, scanWork)
})

// 如果是最后一个mark worker,param为当前g的指针
completed := gp.param != nil
// 重置
gp.param = nil
// mark已结束
if completed {
//
gcMarkDone()
}

// 仍有欠债
if gp.gcAssistBytes < 0 {
// 偷不到额度,也没有完成足够的任务偿还欠债
// 被抢占
if gp.preempt {
// 同协程yield关键字,当前g让出CPU,g0执行调度运行其他g,非抢占
Gosched()
// 被唤醒
// 重试
goto retry
}

// 全局bgScanCredit没有额度则把g放到assist队列并挂起休眠,否则直接返回
if !gcParkAssist() {
// 有额度,不挂起休眠
// 重试
goto retry
}

// 有新的额度,被唤醒
}

// 如果先前已经协助GC标记 => return前把相关字段复原
if enteredMarkAssistForTracing {
// 不协助GC标记,降低GC对CPU的使用(trace相关的代码我删了)
gp.inMarkAssist = false
}

// 结束了
}

// 协助GC标记
func gcAssistAlloc1(gp *g, scanWork int64) {
// 重置,需要利用param字段传递数据
gp.param = nil

// GC未启动/停止
if atomic.Load(&gcBlackenEnabled) == 0 {
// 欠债归0
gp.gcAssistBytes = 0
return
}

// 开始时刻
startTime := nanotime()
// stamp存储limiterEventMarkAssist和startTime
trackLimiterEvent := gp.m.p.ptr().limiterEvent.start(limiterEventMarkAssist, startTime)

// 启动worker,计数器减1
decnwait := atomic.Xadd(&work.nwait, -1)
// 异常,nproc始终>=nwait
if decnwait == work.nproc {
println("runtime: work.nwait =", decnwait, "work.nproc=", work.nproc)
throw("nwait > work.nprocs")
}

// 改为_Gwaiting状态并设置waitreason,可抢占
casGToWaitingForGC(gp, _Grunning, waitReasonGCAssistMarking)

// wbuf
gcw := &getg().m.p.ptr().gcw
// 从本地队列获取并扫描灰色对象,或扫描根对象,直到达到指定额度
// (workDone可能大于scanWork,也可能因为mark阶段结束而小于scanWork)
workDone := gcDrainN(gcw, scanWork)

// 从_Gwaiting状态改为_Grunning(非_Grunnable)
casgstatus(gp, _Gwaiting, _Grunning)

// 转换参数
assistBytesPerWork := gcController.assistBytesPerWork.Load()
// 额度调整,这里加1是向上取整
gp.gcAssistBytes += 1 + int64(assistBytesPerWork*float64(workDone))

// 复原,worker计数器加1
incnwait := atomic.Xadd(&work.nwait, +1)
// 异常,nproc始终>=nwait
if incnwait > work.nproc {
println("runtime: work.nwait=", incnwait,
"work.nproc=", work.nproc)
throw("work.nwait > work.nproc")
}

// 队列为空(最后一个mark worker) and 没有标记任务可以执行
if incnwait == work.nproc && !gcMarkWorkAvailable(nil) {
// 指针
gp.param = unsafe.Pointer(gp)
}

// 当前时刻
now := nanotime()
// 耗时
duration := now - startTime
// p
pp := gp.m.p.ptr()
// 累计到gcAssistTime
pp.gcAssistTime += duration

if trackLimiterEvent {
// 重置stamp字段,纪录耗时
pp.limiterEvent.stop(limiterEventMarkAssist, now)
}

// > 5000ns => gcAssistTime每达到5000ns则刷新到全局计数器里
if pp.gcAssistTime > gcAssistTimeSlack {
// 累计到全局GC助攻累计耗时
gcController.assistTime.Add(pp.gcAssistTime)
// 尝试加锁并调用updateLocked
gcCPULimiter.update(now)
// 重置
pp.gcAssistTime = 0
}
}

// 全局bgScanCredit没有额度则把g放到assist队列并挂起休眠,否则直接返回
// 与gcFlushBgCredit成对使用
func gcParkAssist() bool {
lock(&work.assistQueue.lock)
// GC未启动/停止
if atomic.Load(&gcBlackenEnabled) == 0 {
unlock(&work.assistQueue.lock)
return true
}

// g
gp := getg()
// 快照
oldList := work.assistQueue.q
// 当前g放到队列末尾
work.assistQueue.q.pushBack(gp)

// 还有额度
if gcController.bgScanCredit.Load() > 0 {
// 用快照恢复队列(是否会有并发问题?)
work.assistQueue.q = oldList
// 队列不为空
if oldList.tail != 0 {
// next指针置为nil
oldList.tail.ptr().schedlink.set(nil)
}
unlock(&work.assistQueue.lock)
return false
}

// 没有额度

// 当前g让出CPU,g0执行调度运行其他g(在内部g、m解除绑定后会解锁lock)
goparkunlock(&work.assistQueue.lock, waitReasonGCAssistWait, traceBlockGCMarkAssist, 2)
// 被唤醒
return true
}

写屏障

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// 新分配对象标记为黑色,类似greyobject
func gcmarknewobject(span *mspan, obj uintptr) {
// debug.gccheckmark默认为0,忽略
if useCheckmark {
throw("gcmarknewobject called while doing checkmark")
}
// 应该在mark阶段
if gcphase == _GCmarktermination {
throw("mallocgc called with gcphase == _GCmarktermination")
}

// 标记对象

// offset-对象在mspan内的位置
objIndex := span.objIndex(obj)
// 返回对象在gcmarkBits的字节索引和字节内offset
// 设置该位(gcmarkBits指定字节、指定位设置为1)
span.markBitsForIndex(objIndex).setMarked()

// 标记mspan

// 通过地址计算出heapArena、页起始索引、页起始位
arena, pageIdx, pageMask := pageIndexOf(span.base())
// 未设置
if arena.pageMarks[pageIdx]&pageMask == 0 {
// 汇编,按位或,看起来只有第一个页有设置
atomic.Or8(&arena.pageMarks[pageIdx], pageMask)
}

// p.gcw
gcw := &getg().m.p.ptr().gcw
gcw.bytesMarked += uint64(span.elemsize)
}

OS内存申请

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
// 向系统申请内存,返回对齐后的内存地址和大小,对齐后剩余的量全部释放回系统
func sysReserveAligned(v unsafe.Pointer, size, align uintptr) (unsafe.Pointer, uintptr) {
// wasm(isSbrkPlatform默认为false)
if isSbrkPlatform {
if v != nil {
throw("unexpected heap arena hint on sbrk platform")
}
return sysReserveAlignedSbrk(size, align)
}

// windows才有,忽略
retries := 0
retry:
// 向系统申请内存(Reserved)
p := uintptr(sysReserve(v, size+align))
switch {
case p == 0: // 申请失败
return nil, 0
case p&(align-1) == 0: // 已对齐
return unsafe.Pointer(p), size + align
case GOOS == "windows": // 忽略
sysFreeOS(unsafe.Pointer(p), size+align)
p = alignUp(p, align)
p2 := sysReserve(unsafe.Pointer(p), size)
if p != uintptr(p2) {
sysFreeOS(p2, size)
if retries++; retries == 100 {
throw("failed to allocate aligned heap memory; too many retries")
}
goto retry
}
return p2, size
default: // 其他情况
// p按align的倍数向上取整
pAligned := alignUp(p, align)
// p和pAligned的间隔部份释放回系统
sysFreeOS(unsafe.Pointer(p), pAligned-p)
// end
end := pAligned + size
// 申请内存的end跟实际end的间隔(align剩余部份)
endLen := (p + size + align) - end
if endLen > 0 {
// align剩余部份也释放回系统
sysFreeOS(unsafe.Pointer(end), endLen)
}
return unsafe.Pointer(pAligned), size
}
}

// 调用sysAlloc向系统申请size大小的内存(Ready),按align倍数向上取整,统计
// 超过64KB直接向系统申请,未超过64KB则一次性申请256KB内存后再分配
func persistentalloc(size, align uintptr, sysStat *sysMemStat) unsafe.Pointer {
var p *notInHeap
systemstack(func() {
// 申请size大小的内存,按align倍数向上取整,统计
p = persistentalloc1(size, align, sysStat)
})
return unsafe.Pointer(p)
}

// 调用sysAlloc向系统申请size大小的内存(Ready),按align倍数向上取整,统计
// 超过64KB直接向系统申请,未超过64KB则一次性申请256KB内存后再分配
func persistentalloc1(size, align uintptr, sysStat *sysMemStat) *notInHeap {
const (
// 64KB
maxBlock = 64 << 10
)

// size不能为0
if size == 0 {
throw("persistentalloc: size == 0")
}

if align != 0 {
// 2的倍数
if align&(align-1) != 0 {
throw("persistentalloc: align is not a power of 2")
}
// 不能大于8K
if align > _PageSize {
throw("persistentalloc: align is too large")
}
} else {
// 默认8字节对齐
align = 8
}

// 超过64KB
if size >= maxBlock {
// 直接向系统申请size大小内存(Ready)
return (*notInHeap)(sysAlloc(size, sysStat))
}

// 未超过64KB
mp := acquirem()
var persistent *persistentAlloc
// p不为空
if mp != nil && mp.p != 0 {
// 使用p的palloc
persistent = &mp.p.ptr().palloc
} else {
// 使用全局alloc
lock(&globalAlloc.mutex)
persistent = &globalAlloc.persistentAlloc
}
// offset按align(默认8字节)的倍数向上取整
persistent.off = alignUp(persistent.off, align)
// offset+size超过256KB or base为nil
if persistent.off+size > persistentChunkSize || persistent.base == nil {
// 直接向系统申请256KB大小的内存(Ready)
persistent.base = (*notInHeap)(sysAlloc(persistentChunkSize, &memstats.other_sys))
// 申请失败
if persistent.base == nil {
if persistent == &globalAlloc.persistentAlloc {
unlock(&globalAlloc.mutex)
}
throw("runtime: cannot allocate memory")
}

// 申请成功
for {
// 旧块:persistentAlloc全局变量指针
chunks := uintptr(unsafe.Pointer(persistentChunks))
// 新块的开头8字节存储旧块的指针
*(*uintptr)(unsafe.Pointer(persistent.base)) = chunks
// 更新persistentAlloc
if atomic.Casuintptr((*uintptr)(unsafe.Pointer(&persistentChunks)), chunks, uintptr(unsafe.Pointer(persistent.base))) {
break
}
}
// offset=8
persistent.off = alignUp(goarch.PtrSize, align)
}
// 移动8个字节指向初始地址
p := persistent.base.add(persistent.off)
// 分配size大小的内存
persistent.off += size
releasem(mp)
// 解锁
if persistent == &globalAlloc.persistentAlloc {
unlock(&globalAlloc.mutex)
}

// 统计
if sysStat != &memstats.other_sys {
sysStat.add(int64(size))
memstats.other_sys.add(-int64(size))
}
return p
}

// 判断地址是否在persistentChunks
func inPersistentAlloc(p uintptr) bool {
chunk := atomic.Loaduintptr((*uintptr)(unsafe.Pointer(&persistentChunks)))
for chunk != 0 {
// 在chunk内存地址范围内(256KB)
if p >= chunk && p < chunk+persistentChunkSize {
return true
}
// 找下一个chunk
chunk = *(*uintptr)(unsafe.Pointer(chunk))
}
// 没找到
return false
}

profile相关

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// 返回堆分析的下一个采样点(随机数:[0,MemProfileRate))
func nextSample() int64 {
if MemProfileRate == 0 {
// 永不采样
return maxInt64
}
if MemProfileRate == 1 {
// 立刻采样
return 0
}

// 忽略
if GOOS == "plan9" {
// plan9在note handler不支持浮点数
if gp := getg(); gp == gp.m.gsignal {
return nextSampleNoFP()
}
}

// 随机数
return int64(fastexprand(MemProfileRate))
}

// 重置相关字段,记录内存分配事件,添加special防止GC回收
func profilealloc(mp *m, x unsafe.Pointer, size uintptr) {
// 获取p.mcache,没有p则获取mcache0
c := getMCache(mp)
// 异常
if c == nil {
throw("profilealloc called without a P or outside bootstrapping")
}
// 重置,默认512KB,可以通过GODEBUG修改
c.memProfRate = MemProfileRate
// 返回堆分析的下一个采样点(随机数:[0,MemProfileRate))
c.nextSample = nextSample()
// 记录内存分配事件,添加special防止GC回收
mProf_Malloc(mp, x, size)
}

其他

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 内存区域批量清0
func memclrNoHeapPointersChunked(size uintptr, x unsafe.Pointer) {
v := uintptr(x)
// 按批次清0,每批256KB
const chunkBytes = 256 * 1024
vsize := v + size
for voff := v; voff < vsize; voff = voff + chunkBytes {
// 被抢占
if getg().preempt {
// 可能持有锁
goschedguarded()
// 被唤醒
}

// 将min(avail, lump)个字节清0
n := vsize - voff
if n > chunkBytes {
n = chunkBytes
}
// 内存区域清0(只有你知道该区域不存在指针才能调用)
memclrNoHeapPointers(unsafe.Pointer(voff), n)
}
}

参考文档

An overview of memory management in Go
A visual guide to Go Memory Allocator from scratch (Golang)
Go: Memory Management and Allocation
Go’s Memory Allocator - Overview
Visualizing memory management in Golang
GopherCon 2018 - Allocator Wrestling
7.1 内存分配器
Contiguous stacks in Go
Golang Memory Management (based on 1.12.5)
Golang Memory Allocator