mspan
page
:最小存储单元
Golang中的页的大小为8KB,Go中是以页为单位向操作系统申请内存的。
mspan
:为最小的管理单元
mspan的大小为page的整数倍 ,从8B到80KB划分成了67 个不同的规格,分配对象的时候,会从按照对象的大小从不同规格的mspan中获取空间
所以mspan可以看成是一个或多个连续的页。
优点:
消除了外部碎片,但是会产生内部碎片
使mcentral支持实现更加细致的锁
同等级的mspan属于同一个mcentral,所以会基于同一把互斥锁
mspan会基于bitMap辅助快速找到空闲内存块(Ctz64算法)
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
type mspan struct {
// 前后节点
next * mspan // next span in list, or nil if none
prev * mspan // previous span in list, or nil if none
list * mSpanList // For debugging. TODO: Remove.
// 起始地址
startAddr uintptr // address of first byte of span aka s.base()
// 包含几页,页是连续的
npages uintptr // number of pages in span
manualFreeList gclinkptr // list of free objects in mSpanManual spans
// freeindex之前的位置都被占用了
freeindex uintptr
// 最多可以存放多少个object
nelems uintptr // number of object in the span.
// ☆ allocaCache中每个bit对应一个object块,标识该块是否被占用
allocCache uint64
allocBits * gcBits
gcmarkBits * gcBits
sweepgen uint32
divMul uint32 // for divide by elemsize
allocCount uint16 // number of allocated objects
// 表示mspan对应的那67个等级中的哪一个
spanclass spanClass // size class and noscan (uint8)
state mSpanStateBox // mSpanInUse etc; accessed atomically (get/set methods)
needzero uint8 // needs to be zeroed before allocation
allocCountBeforeCache uint16 // a copy of allocCount that is stored just before this span is cached
elemsize uintptr // computed from sizeclass or from npages
limit uintptr // end of data in span
speciallock mutex // guards specials list
specials * special // linked list of special records sorted by offset.
}
next/prev:指的是后一个和前一个Span,说明Span是一个双向链表
startAddr:当前Span的起始地址。
npages:当前Span中一共含有多少页
nelems:当前Span中存储的对象的个数
spanclass:当前Span的等级
spanClass
Go语言将内存分成了67 个级别,其中0代表大小不固定的大对象 ,普通对象在分配内存的时候是更具下面这个表来按照最合适的大小来分配的:
class
bytes/obj
bytes/span
objects
tail waste
max waste
min align
1
8
8192
1024
0
87.50%
8
2
16
8192
512
0
43.75%
16
3
24
8192
341
8
29.24%
8
4
32
8192
256
0
21.88%
32
5
48
8192
170
32
31.52%
16
6
64
8192
128
0
23.44%
64
7
80
8192
102
32
19.07%
16
8
96
8192
85
32
15.95%
32
9
112
8192
73
16
13.56%
16
10
128
8192
64
0
11.72%
128
11
144
8192
56
128
11.82%
16
12
160
8192
51
32
9.73%
32
13
176
8192
46
96
9.59%
16
14
192
8192
42
128
9.25%
64
15
208
8192
39
80
8.12%
16
16
224
8192
36
128
8.15%
32
17
240
8192
34
32
6.62%
16
18
256
8192
32
0
5.86%
256
19
288
8192
28
128
12.16%
32
20
320
8192
25
192
11.80%
64
21
352
8192
23
96
9.88%
32
22
384
8192
21
128
9.51%
128
23
416
8192
19
288
10.71%
32
24
448
8192
18
128
8.37%
64
25
480
8192
17
32
6.82%
32
26
512
8192
16
0
6.05%
512
27
576
8192
14
128
12.33%
64
28
640
8192
12
512
15.48%
128
29
704
8192
11
448
13.93%
64
30
768
8192
10
512
13.94%
256
31
896
8192
9
128
15.52%
128
32
1024
8192
8
0
12.40%
1024
33
1152
8192
7
128
12.41%
128
34
1280
8192
6
512
15.55%
256
35
1408
16384
11
896
14.00%
128
36
1536
8192
5
512
14.00%
512
37
1792
16384
9
256
15.57%
256
38
2048
8192
4
0
12.45%
2048
39
2304
16384
7
256
12.46%
256
40
2688
8192
3
128
15.59%
128
41
3072
24576
8
0
12.47%
1024
42
3200
16384
5
384
6.22%
128
43
3456
24576
7
384
8.83%
128
44
4096
8192
2
0
15.60%
4096
45
4864
24576
5
256
16.65%
256
46
5376
16384
3
256
10.92%
256
47
6144
24576
4
0
12.48%
2048
48
6528
32768
5
128
6.23%
128
49
6784
40960
6
256
4.36%
128
50
6912
49152
7
768
3.37%
256
51
8192
8192
1
0
15.61%
8192
52
9472
57344
6
512
14.28%
256
53
9728
49152
5
512
3.64%
512
54
10240
40960
4
0
4.99%
2048
55
10880
32768
3
128
6.24%
128
56
12288
24576
2
0
11.45%
4096
57
13568
40960
3
256
9.99%
256
58
14336
57344
4
0
5.35%
2048
59
16384
16384
1
0
12.49%
8192
60
18432
73728
4
0
11.11%
2048
61
19072
57344
3
128
3.57%
128
62
20480
40960
2
0
6.87%
4096
63
21760
65536
3
256
6.25%
256
64
24576
24576
1
0
11.45%
8192
65
27264
81920
3
128
10.00%
128
66
28672
57344
2
0
4.91%
4096
67
32768
32768
1
0
12.50%
8192
上面的数据在runtime/sizeclasses.go
中有记载。
class:span的等级
bytes/obj:该级别的Span中对象的最大 大小,比如说第一级别中代表对象的大小只能是:1~8个字节。第二级别代表对象的大小只能是9~16个字节。
objects:该级别的Span中最多能存储多少个对象。
tail waste:尾部浪费。即对象的大小没有达到该级别的最大的大小而导致的内部碎片的浪费。
max waste:最大浪费率
如果一个对象的大小是17个byte,则会分配给class为3的mspan。如果一个对象的大小为36,则会分配给class为5的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
// spanClass 表示 span 的大小类别和无扫描(noscan)属性。
//
// 每个大小类别都有一个 noscan spanClass 和一个 scan spanClass。
// noscan spanClass 仅包含 noscan 对象,这些对象不包含指针,
// 因此不需要由垃圾收集器扫描。
type spanClass uint8
// makeSpanClass 根据大小类别(sizeclass)和无扫描(noscan)属性创建 spanClass。
// sizeclass 是一个表示大小类别的 uint8 值。
// noscan 是一个布尔值,表示该 spanClass 是否为 noscan。
func makeSpanClass ( sizeclass uint8 , noscan bool ) spanClass {
// 将 sizeclass 左移 1 位,然后将 noscan 转换为整数后做按位或运算,生成最终的 spanClass。
return spanClass ( sizeclass << 1 ) | spanClass ( bool2int ( noscan ))
}
// 获取当前的等级
func ( sc spanClass ) sizeclass () int8 {
return int8 ( sc >> 1 )
}
// 是否是noscan
func ( sc spanClass ) noscan () bool {
return sc & 1 != 0
}
spanClass是uint8类型的,其中高7位用于表示span的等级,最低位表示noscan,noscan表示了object中是否包含指针,在gc的时候有用。
mcache
线程级别。
src/runtime/mcache.go
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
const (
// 136
numSpanClasses = _NumSizeClasses << 1
tinySpanClass = spanClass ( tinySizeClass << 1 | 1 )
)
// 每个线程(在 Go 中,每个 P)的缓存用于小对象。
// 包含一个小对象缓存和本地分配统计。
// 由于它是每个线程(每个 P)的,所以不需要锁定。
//
// mcaches 是从非 GC 管理的内存中分配的,因此任何堆指针必须特别处理。
//
//go:notinheap
type mcache struct {
// 以下成员在每次 malloc 时都会被访问,
// 因此将它们放在一起以便更好地缓存。
nextSample uintptr // 在分配了这么多字节之后触发堆采样
scanAlloc uintptr // 分配的可扫描堆的字节数
// 【没有指针的小对象】的分配器缓存。
// 请参见 malloc.go 中的 "Tiny allocator" 注释。
// tiny 指向当前 tiny 块的开头,
// 如果没有当前 tiny 块,则为 nil。
//
// tiny 是一个堆指针。由于 mcache 位于非 GC 管理的内存中,
// 我们通过在标记终止期间在 releaseAll 中清除它来处理它。
//
// tinyAllocs 是拥有此 mcache 的 P 执行的 tiny 分配的次数。
// 指向堆中的一片内存
tiny uintptr
// 下一个空闲内存所在的偏移量
tinyoffset uintptr
// 分配的次数
tinyAllocs uintptr
// 其余部分在每次 malloc 时不会被访问。
alloc [ numSpanClasses ] * mspan // 根据 spanClass 索引分配用的 span
stackcache [ _NumStackOrders ] stackfreelist // 用于缓存栈的空闲列表
// flushGen 表示上次刷新此 mcache 的 sweepgen。
// 如果 flushGen != mheap_.sweepgen,则此 mcache 中的 span 是陈旧的,
// 需要刷新以便可以被清扫。这在 acquirep 中完成。
flushGen uint32
}
mcentral
src\runtime\mcentral.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 给定大小的空闲对象的中央列表。
//
//go:notinheap
type mcentral struct {
spanclass spanClass
// partial 和 full 包含两组 mspan 集合:一组是已经清扫的在使用的 span,
// 另一组是尚未清扫的在使用的 span。这两组在每个 GC 周期中交换角色。
// 未清扫集合在每个 GC 周期中要么通过分配要么通过后台清扫器被清空,
// 因此只需要两组。
//
// sweepgen 在每个 GC 周期中增加 2,因此清扫过的 span 位于
// partial[sweepgen/2%2] 中,而未清扫的 span 位于
// partial[1-sweepgen/2%2] 中。清扫会从未清扫集合中弹出 span,
// 并将仍在使用的 span 推入清扫过的集合。同样,分配一个在使用的 span
// 会将其推入清扫过的集合。
//
// 清扫器的某些部分可以清扫任意 span,因此不能从未清扫集合中移除它们,
// 但会将 span 添加到适当的清扫列表中。因此,从未清扫列表中消耗的
// 清扫器和 mcentral 部分可能会遇到清扫过的 span,这些应被忽略。
partial [ 2 ] spanSet // 有空位的mspan集合,数组长度为2是为了抗一轮GC
full [ 2 ] spanSet // 没有空位的mspan集合
}
每个mcentral对应一中spanClass,
每个mcentral下聚合了该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
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
// 主堆分配器。
// 堆本身是 "free" 和 "scav" treaps,但所有其他全局数据也在这里。
//
// mheap 不能堆分配,因为它包含 mSpanLists,这些不能堆分配。
//
//go:notinheap
type mheap struct {
// lock 只能在系统栈上获取,否则如果 g 的栈在持有锁时增长,可能会自死锁。
lock mutex
_ uint32 // 8字节对齐 pages,以使其与测试对齐。
// 空闲页分配器,底层是多个基数树组成的索引,每棵树对应的空间为16GB
pages pageAlloc // 页分配数据结构
sweepgen uint32 // 清扫代,见 mspan 中的注释;在 STW(stop-the-world)期间写入
// allspans 是所有曾经创建的 mspan 的切片。每个 mspan 只出现一次。
//
// allspans 的内存是手动管理的,并且可以随着堆的增长重新分配和移动。
//
// 通常,allspans 由 mheap_.lock 保护,这可以防止并发访问和释放底层存储。
// 在 STW 期间的访问可能不会持有锁,但必须确保在访问时不能发生分配
// (因为这可能会释放底层存储)。
// 记录了所有的 mspan. 需要知道,所有 mspan 都是经由 mheap,使用连续空闲页组装生成的
allspans [] * mspan // 所有的 span
// Proportional sweep(比例清扫)
//
// 这些参数表示一个从 gcController.heapLive 到页面清扫计数的线性函数。
// 比例清扫系统通过保持当前页面清扫计数高于当前 gcController.heapLive
// 时的这条线来维持在黑色。
//
// 该线的斜率为 sweepPagesPerByte,并且通过一个基点 (sweepHeapLiveBasis,
// pagesSweptBasis)。系统在任何给定时间点处于 (gcController.heapLive,
// pagesSwept)。
//
// 重要的是,线必须通过我们控制的点,而不仅仅是从 0,0 原点开始,
// 因为这让我们可以在任何时候调整清扫节奏,同时考虑当前进度。如果我们只能
// 调整斜率,那么如果已经取得了一些进展,会在债务上产生不连续性。
pagesInUse atomic . Uint64 // 在 mSpanInUse 中统计的 span 页数
pagesSwept atomic . Uint64 // 本周期清扫的页数
pagesSweptBasis atomic . Uint64 // 用作清扫比率原点的 pagesSwept 值
sweepHeapLiveBasis uint64 // 用作清扫比率原点的 gcController.heapLive 值;带锁写入,不带锁读取
sweepPagesPerByte float64 // 比例清扫比率;带锁写入,不带锁读取
// TODO(austin): pagesInUse 应该是 uintptr,但 386 编译器不能 8 字节对齐字段。
// 页面回收状态
// reclaimIndex 是 allArenas 中下一个要回收的页面索引。
// 具体来说,它指的是 allArenas[i / pagesPerArena] 中的第 (i %
// pagesPerArena) 页。
//
// 如果此值 >= 1<<63,页面回收器已完成扫描页面标记。
reclaimIndex atomic . Uint64
// reclaimCredit 是多余清扫页面的备用积分。由于页面回收器以大块工作,
// 它可能回收比请求更多的页面。任何释放的备用页面都会进入这个积分池。
reclaimCredit atomic . Uintptr
// arenas 是堆 arena 映射。它指向整个可用虚拟地址空间中每个 arena 帧的元数据。
//
// 使用 arenaIndex 计算该数组的索引。
//
// 对于未由 Go 堆支持的地址空间区域,arena 映射包含 nil。
//
// 修改由 mheap_.lock 保护。读取可以不加锁进行;但是,当未持有锁时,
// 给定条目可能随时从 nil 变为非 nil。(条目不会变回 nil。)
//
// 通常,这是一个两级映射,包括一个 L1 映射和可能的多个 L2 映射。
// 当有大量 arena 帧时,这节省了空间。然而,在许多平台上(即使是 64 位),
// arenaL1Bits 为 0,这使得它实际上是单级映射。在这种情况下,arenas[0]
// 永远不会是 nil。
arenas [ 1 << arenaL1Bits ] * [ 1 << arenaL2Bits ] * heapArena
// heapArenaAlloc 是用于分配 heapArena 对象的预留空间。这仅在 32 位上使用,
// 我们预留此空间以避免与堆本身交错。
heapArenaAlloc linearAlloc
// arenaHints 是一个列表,包含尝试添加更多堆 arena 的地址。最初用一组
// 通用提示地址填充,并随着实际堆 arena 范围的边界增长。
arenaHints * arenaHint
// arena 是用于分配堆 arena(实际的 arena)的预留空间。这仅在 32 位上使用。
arena linearAlloc
// allArenas 是所有映射的 arena 的 arenaIndex。可以用它遍历地址空间。
//
// 访问由 mheap_.lock 保护。然而,由于这是追加的,旧的底层数组永远不会被释放,
// 所以可以安全地获取 mheap_.lock,复制切片头,然后释放 mheap_.lock。
allArenas [] arenaIdx
// sweepArenas 是在清扫周期开始时拍摄的 allArenas 快照。只需阻止 GC
// (通过禁用抢占)即可安全读取。
sweepArenas [] arenaIdx
// markArenas 是在标记周期开始时拍摄的 allArenas 快照。由于 allArenas
// 仅追加,因此在标记期间此切片及其内容不会更改,因此可以安全读取。
markArenas [] arenaIdx
// curArena 是堆当前正在扩展的 arena。应始终是 physPageSize 对齐的。
curArena struct {
base , end uintptr
}
_ uint32 // 确保 central 的 64 位对齐
// 小尺寸类别的中央空闲列表。
// padding 确保 mcentral 被 CacheLinePadSize 字节隔开,
// 以便每个 mcentral.lock 拥有自己的缓存行。
// central 由 spanClass 索引。 numSpanClasses=136
central [ numSpanClasses ] struct {
mcentral mcentral
// 用于内存对齐
pad [ cpu . CacheLinePadSize - unsafe . Sizeof ( mcentral {}) % cpu . CacheLinePadSize ] byte
}
spanalloc fixalloc // span* 的分配器
cachealloc fixalloc // mcache* 的分配器
specialfinalizeralloc fixalloc // specialfinalizer* 的分配器
specialprofilealloc fixalloc // specialprofile* 的分配器
specialReachableAlloc fixalloc // specialReachable 的分配器
speciallock mutex // special 记录分配器的锁。
arenaHintAlloc fixalloc // arenaHints 的分配器
unused * specialfinalizer // 从未设置,仅此处以强制将 specialfinalizer 类型放入 DWARF
}
heapArena
Go语言中将堆 划分成多个arena,在amd64的架构下,每个arena的区域大小是64MB,每个arena都对应一个heapArena相当于头,用来存储arena的元数据,
heapArena记录了页到mspan的映射关系,应为在GC的时候,通过地址便宜找页很方便,但是找到其所属的mspan则不是那么方便,所以通过映射关系来维护。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
type heapArena struct {
//
bitmap [ heapArenaBitmapBytes ] byte
spans [ pagesPerArena ] * mspan
pageInUse [ pagesPerArena / 8 ] uint8
pageMarks [ pagesPerArena / 8 ] uint8
pageSpecials [ pagesPerArena / 8 ] uint8
checkmarks * checkmarksMap
zeroedBase uintptr
}
bitmap
:
spans
:将当前arena中的页面映射到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
type pallocSum uint64
// packPallocSum takes a start, max, and end value and produces a pallocSum.
func packPallocSum ( start , max , end uint ) pallocSum {
if max == maxPackedValue {
return pallocSum ( uint64 ( 1 << 63 ))
}
return pallocSum (( uint64 ( start ) & ( maxPackedValue - 1 )) |
(( uint64 ( max ) & ( maxPackedValue - 1 )) << logMaxPackedValue ) |
(( uint64 ( end ) & ( maxPackedValue - 1 )) << ( 2 * logMaxPackedValue )))
}
// start extracts the start value from a packed sum.
func ( p pallocSum ) start () uint {
if uint64 ( p ) & uint64 ( 1 << 63 ) != 0 {
return maxPackedValue
}
return uint ( uint64 ( p ) & ( maxPackedValue - 1 ))
}
// max extracts the max value from a packed sum.
func ( p pallocSum ) max () uint {
if uint64 ( p ) & uint64 ( 1 << 63 ) != 0 {
return maxPackedValue
}
return uint (( uint64 ( p ) >> logMaxPackedValue ) & ( maxPackedValue - 1 ))
}
// end extracts the end value from a packed sum.
func ( p pallocSum ) end () uint {
if uint64 ( p ) & uint64 ( 1 << 63 ) != 0 {
return maxPackedValue
}
return uint (( uint64 ( p ) >> ( 2 * logMaxPackedValue )) & ( maxPackedValue - 1 ))
}
// unpack unpacks all three values from the summary.
func ( p pallocSum ) unpack () ( uint , uint , uint ) {
if uint64 ( p ) & uint64 ( 1 << 63 ) != 0 {
return maxPackedValue , maxPackedValue , maxPackedValue
}
return uint ( uint64 ( p ) & ( maxPackedValue - 1 )),
uint (( uint64 ( p ) >> logMaxPackedValue ) & ( maxPackedValue - 1 )),
uint (( uint64 ( p ) >> ( 2 * logMaxPackedValue )) & ( maxPackedValue - 1 ))
}
pallocSum位64位整型,其中最高位不使用,剩下的63位分成三个部分,每个部分21位,分别代表:start,max,end。
mheap会基于bitMap表示内存中各页的使用情况:
bit位为0:表示该页是空闲的
bit位为1:表示该页已被mspan占用
每棵基数树代表了16GB的内存空间中各个页的使用情况,用于帮助mheap能够快速的定位到指定的可以使用的空闲页
mheap共包含了2^14课基数树,所以能够覆盖到的最大内存空间为256T
内存分配过程
Go的对内存分配采用了tcmalloc内存分配器类似的算法。会按照一组预制的代销规格把内存划分成块,然后把不同大小的内存放到对应的空闲列表中。如:8字节,16字节,24字节,32字节,48字节等等。
对象分类
微对象:(0, 16B), 先用微型分配器奉陪,在尝试mcache、mcentral和堆分配
分配流程:
从P的专属mcache的tiny分配器取内存(无锁)
根据所属的spanClass,从P专属的mcache缓存中区内存(无锁)
根据所属的spanClass,从对应的mcentral中取mspan填充到mcache中,然后从mspan中取内存(spanClass粒度)
根据所属的spanClass,从mheap的页分配器pageAlloc取得足够数量的空闲页组装成mspan,填充到mspan,然后从mspan中取内存(全局锁)
mheap想操作系统申请内存,然后从mspan中取内存(全局锁)
小对象:[16B, 32KB],依次尝试使用mcache,再尝试mcentral和堆分配
分配流程:
跳过第1步
大对象:(32KB, +∞),直接在堆上分配
分配流程:
跳过1~3步
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
func mallocgc ( size uintptr , typ * _type , needzero bool ) unsafe . Pointer {
// ...
// 获取 m
mp := acquirem ()
// 获取当前 p 对应的 mcache
c := getMCache ( mp )
var span * mspan
var x unsafe . Pointer
// 根据当前对象是否包含指针,标识 gc 时是否需要展开扫描
noscan := typ == nil || typ . ptrdata == 0
// 是否是小于 32KB 的微、小对象
if size <= maxSmallSize {
// 小于 16 B 且无指针,则视为微对象
if noscan && size < maxTinySize {
// tiny 内存块中,从 offset 往后有空闲位置
off := c . tinyoffset
// 如果大小为 5 ~ 8 B,size 会被调整为 8 B,此时 8 & 7 == 0,会走进此分支
if size & 7 == 0 {
// 将 offset 补齐到 8 B 倍数的位置
off = alignUp ( off , 8 )
// 如果大小为 3 ~ 4 B,size 会被调整为 4 B,此时 4 & 3 == 0,会走进此分支
} else if size & 3 == 0 {
// 将 offset 补齐到 4 B 倍数的位置
off = alignUp ( off , 4 )
// 如果大小为 1 ~ 2 B,size 会被调整为 2 B,此时 2 & 1 == 0,会走进此分支
} else if size & 1 == 0 {
// 将 offset 补齐到 2 B 倍数的位置
off = alignUp ( off , 2 )
}
// 如果当前 tiny 内存块空间还够用,则直接分配并返回
if off + size <= maxTinySize && c . tiny != 0 {
// 分配空间
x = unsafe . Pointer ( c . tiny + off )
c . tinyoffset = off + size
c . tinyAllocs ++
mp . mallocing = 0
releasem ( mp )
return x
}
// 分配一个新的 tiny 内存块
span = c . alloc [ tinySpanClass ]
// 从 mCache 中获取
v := nextFreeFast ( span )
if v == 0 {
// 从 mCache 中获取失败,则从 mCentral 或者 mHeap 中获取进行兜底
v , span , shouldhelpgc = c . nextFree ( tinySpanClass )
}
// 分配空间
x = unsafe . Pointer ( v )
( * [ 2 ] uint64 )( x )[ 0 ] = 0
( * [ 2 ] uint64 )( x )[ 1 ] = 0
size = maxTinySize
} else {
// 根据对象大小,映射到其所属的 span 的等级(0~66)
var sizeclass uint8
if size <= smallSizeMax - 8 {
sizeclass = size_to_class8 [ divRoundUp ( size , smallSizeDiv )]
} else {
sizeclass = size_to_class128 [ divRoundUp ( size - smallSizeMax , largeSizeDiv )]
}
// 对应 span 等级下,分配给每个对象的空间大小(0~32KB)
size = uintptr ( class_to_size [ sizeclass ])
// 创建 spanClass 标识,其中前 7 位对应为 span 的等级(0~66),最后标识表示了这个对象 gc 时是否需要扫描
spc := makeSpanClass ( sizeclass , noscan )
// 获取 mcache 中的 span
span = c . alloc [ spc ]
// 从 mcache 的 span 中尝试获取空间
v := nextFreeFast ( span )
if v == 0 {
// mcache 分配空间失败,则通过 mcentral、mheap 兜底
v , span , shouldhelpgc = c . nextFree ( spc )
}
// 分配空间
x = unsafe . Pointer ( v )
// ...
}
// 大于 32KB 的大对象
} else {
// 从 mheap 中获取 0 号 span
span = c . allocLarge ( size , noscan )
span . freeindex = 1
span . allocCount = 1
size = span . elemsize
// 分配空间
x = unsafe . Pointer ( span . base ())
}
// ...
return x
}