Golang中内存分配与管理机制
mspan
-
page:最小存储单元Golang中的页的大小为8KB,Go中是以页为单位向操作系统申请内存的。
-
mspan:为最小的管理单元mspan的大小为page的整数倍,从8B到80KB划分成了67个不同的规格,分配对象的时候,会从按照对象的大小从不同规格的mspan中获取空间
所以mspan可以看成是一个或多个连续的页。
优点:
- 消除了外部碎片,但是会产生内部碎片
- 使mcentral支持实现更加细致的锁
同等级的mspan属于同一个mcentral,所以会基于同一把互斥锁
mspan会基于bitMap辅助快速找到空闲内存块(Ctz64算法)
1type mspan struct {
2 // 前后节点
3 next *mspan // next span in list, or nil if none
4 prev *mspan // previous span in list, or nil if none
5
6 list *mSpanList // For debugging. TODO: Remove.
7
8 // 起始地址
9 startAddr uintptr // address of first byte of span aka s.base()
10 // 包含几页,页是连续的
11 npages uintptr // number of pages in span
12 manualFreeList gclinkptr // list of free objects in mSpanManual spans
13
14 // freeindex之前的位置都被占用了
15 freeindex uintptr
16 // 最多可以存放多少个object
17 nelems uintptr // number of object in the span.
18
19 // ☆ allocaCache中每个bit对应一个object块,标识该块是否被占用
20 allocCache uint64
21 allocBits *gcBits
22
23 gcmarkBits *gcBits
24 sweepgen uint32
25 divMul uint32 // for divide by elemsize
26 allocCount uint16 // number of allocated objects
27
28 // 表示mspan对应的那67个等级中的哪一个
29 spanclass spanClass // size class and noscan (uint8)
30
31 state mSpanStateBox // mSpanInUse etc; accessed atomically (get/set methods)
32 needzero uint8 // needs to be zeroed before allocation
33 allocCountBeforeCache uint16 // a copy of allocCount that is stored just before this span is cached
34 elemsize uintptr // computed from sizeclass or from npages
35 limit uintptr // end of data in span
36 speciallock mutex // guards specials list
37 specials *special // linked list of special records sorted by offset.
38}
- 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// spanClass 表示 span 的大小类别和无扫描(noscan)属性。
2//
3// 每个大小类别都有一个 noscan spanClass 和一个 scan spanClass。
4// noscan spanClass 仅包含 noscan 对象,这些对象不包含指针,
5// 因此不需要由垃圾收集器扫描。
6type spanClass uint8
7
8
9// makeSpanClass 根据大小类别(sizeclass)和无扫描(noscan)属性创建 spanClass。
10// sizeclass 是一个表示大小类别的 uint8 值。
11// noscan 是一个布尔值,表示该 spanClass 是否为 noscan。
12func makeSpanClass(sizeclass uint8, noscan bool) spanClass {
13 // 将 sizeclass 左移 1 位,然后将 noscan 转换为整数后做按位或运算,生成最终的 spanClass。
14 return spanClass(sizeclass<<1) | spanClass(bool2int(noscan))
15}
16
17
18// 获取当前的等级
19func (sc spanClass) sizeclass() int8 {
20 return int8(sc >> 1)
21}
22
23// 是否是noscan
24func (sc spanClass) noscan() bool {
25 return sc&1 != 0
26}
spanClass是uint8类型的,其中高7位用于表示span的等级,最低位表示noscan,noscan表示了object中是否包含指针,在gc的时候有用。
mcache
线程级别。
src/runtime/mcache.go
1const (
2 // 136
3 numSpanClasses = _NumSizeClasses << 1
4 tinySpanClass = spanClass(tinySizeClass<<1 | 1)
5)
6
7
8// 每个线程(在 Go 中,每个 P)的缓存用于小对象。
9// 包含一个小对象缓存和本地分配统计。
10// 由于它是每个线程(每个 P)的,所以不需要锁定。
11//
12// mcaches 是从非 GC 管理的内存中分配的,因此任何堆指针必须特别处理。
13//
14//go:notinheap
15type mcache struct {
16 // 以下成员在每次 malloc 时都会被访问,
17 // 因此将它们放在一起以便更好地缓存。
18 nextSample uintptr // 在分配了这么多字节之后触发堆采样
19 scanAlloc uintptr // 分配的可扫描堆的字节数
20
21 // 【没有指针的小对象】的分配器缓存。
22 // 请参见 malloc.go 中的 "Tiny allocator" 注释。
23
24 // tiny 指向当前 tiny 块的开头,
25 // 如果没有当前 tiny 块,则为 nil。
26 //
27 // tiny 是一个堆指针。由于 mcache 位于非 GC 管理的内存中,
28 // 我们通过在标记终止期间在 releaseAll 中清除它来处理它。
29 //
30 // tinyAllocs 是拥有此 mcache 的 P 执行的 tiny 分配的次数。
31 // 指向堆中的一片内存
32 tiny uintptr
33 // 下一个空闲内存所在的偏移量
34 tinyoffset uintptr
35 // 分配的次数
36 tinyAllocs uintptr
37
38 // 其余部分在每次 malloc 时不会被访问。
39 alloc [numSpanClasses]*mspan // 根据 spanClass 索引分配用的 span
40
41 stackcache [_NumStackOrders]stackfreelist // 用于缓存栈的空闲列表
42
43 // flushGen 表示上次刷新此 mcache 的 sweepgen。
44 // 如果 flushGen != mheap_.sweepgen,则此 mcache 中的 span 是陈旧的,
45 // 需要刷新以便可以被清扫。这在 acquirep 中完成。
46 flushGen uint32
47}
-
是Go语言中的线程缓存,他会与线程上的处理器P绑定,主要用来缓存用户程序申请的微小对象。
-
每个线程缓存都持有
136 (68(大小维度) * 2(noscan维度))个mspan,位于字段alloc中
-
mcache中有一个tiny allocator
tiny:指向堆中的一片内存tinyoffset: 下一个空闲内存所在的偏移量tinyAllocs:分配的次数- 用于处理小于16B的对象
- 只会用于分配非指针类型的对象
mcentral
src\runtime\mcentral.go
1// 给定大小的空闲对象的中央列表。
2//
3//go:notinheap
4type mcentral struct {
5 spanclass spanClass
6
7 // partial 和 full 包含两组 mspan 集合:一组是已经清扫的在使用的 span,
8 // 另一组是尚未清扫的在使用的 span。这两组在每个 GC 周期中交换角色。
9 // 未清扫集合在每个 GC 周期中要么通过分配要么通过后台清扫器被清空,
10 // 因此只需要两组。
11 //
12 // sweepgen 在每个 GC 周期中增加 2,因此清扫过的 span 位于
13 // partial[sweepgen/2%2] 中,而未清扫的 span 位于
14 // partial[1-sweepgen/2%2] 中。清扫会从未清扫集合中弹出 span,
15 // 并将仍在使用的 span 推入清扫过的集合。同样,分配一个在使用的 span
16 // 会将其推入清扫过的集合。
17 //
18 // 清扫器的某些部分可以清扫任意 span,因此不能从未清扫集合中移除它们,
19 // 但会将 span 添加到适当的清扫列表中。因此,从未清扫列表中消耗的
20 // 清扫器和 mcentral 部分可能会遇到清扫过的 span,这些应被忽略。
21 partial [2]spanSet // 有空位的mspan集合,数组长度为2是为了抗一轮GC
22 full [2]spanSet // 没有空位的mspan集合
23}
- 每个mcentral对应一中spanClass,
- 每个mcentral下聚合了该spanClass
mheap
全局堆缓存
1// 主堆分配器。
2// 堆本身是 "free" 和 "scav" treaps,但所有其他全局数据也在这里。
3//
4// mheap 不能堆分配,因为它包含 mSpanLists,这些不能堆分配。
5//
6//go:notinheap
7type mheap struct {
8 // lock 只能在系统栈上获取,否则如果 g 的栈在持有锁时增长,可能会自死锁。
9 lock mutex
10
11 _ uint32 // 8字节对齐 pages,以使其与测试对齐。
12
13 // 空闲页分配器,底层是多个基数树组成的索引,每棵树对应的空间为16GB
14 pages pageAlloc // 页分配数据结构
15
16 sweepgen uint32 // 清扫代,见 mspan 中的注释;在 STW(stop-the-world)期间写入
17
18 // allspans 是所有曾经创建的 mspan 的切片。每个 mspan 只出现一次。
19 //
20 // allspans 的内存是手动管理的,并且可以随着堆的增长重新分配和移动。
21 //
22 // 通常,allspans 由 mheap_.lock 保护,这可以防止并发访问和释放底层存储。
23 // 在 STW 期间的访问可能不会持有锁,但必须确保在访问时不能发生分配
24 // (因为这可能会释放底层存储)。
25 // 记录了所有的 mspan. 需要知道,所有 mspan 都是经由 mheap,使用连续空闲页组装生成的
26 allspans []*mspan // 所有的 span
27
28 // Proportional sweep(比例清扫)
29 //
30 // 这些参数表示一个从 gcController.heapLive 到页面清扫计数的线性函数。
31 // 比例清扫系统通过保持当前页面清扫计数高于当前 gcController.heapLive
32 // 时的这条线来维持在黑色。
33 //
34 // 该线的斜率为 sweepPagesPerByte,并且通过一个基点 (sweepHeapLiveBasis,
35 // pagesSweptBasis)。系统在任何给定时间点处于 (gcController.heapLive,
36 // pagesSwept)。
37 //
38 // 重要的是,线必须通过我们控制的点,而不仅仅是从 0,0 原点开始,
39 // 因为这让我们可以在任何时候调整清扫节奏,同时考虑当前进度。如果我们只能
40 // 调整斜率,那么如果已经取得了一些进展,会在债务上产生不连续性。
41 pagesInUse atomic.Uint64 // 在 mSpanInUse 中统计的 span 页数
42 pagesSwept atomic.Uint64 // 本周期清扫的页数
43 pagesSweptBasis atomic.Uint64 // 用作清扫比率原点的 pagesSwept 值
44 sweepHeapLiveBasis uint64 // 用作清扫比率原点的 gcController.heapLive 值;带锁写入,不带锁读取
45 sweepPagesPerByte float64 // 比例清扫比率;带锁写入,不带锁读取
46 // TODO(austin): pagesInUse 应该是 uintptr,但 386 编译器不能 8 字节对齐字段。
47
48 // 页面回收状态
49
50 // reclaimIndex 是 allArenas 中下一个要回收的页面索引。
51 // 具体来说,它指的是 allArenas[i / pagesPerArena] 中的第 (i %
52 // pagesPerArena) 页。
53 //
54 // 如果此值 >= 1<<63,页面回收器已完成扫描页面标记。
55 reclaimIndex atomic.Uint64
56
57 // reclaimCredit 是多余清扫页面的备用积分。由于页面回收器以大块工作,
58 // 它可能回收比请求更多的页面。任何释放的备用页面都会进入这个积分池。
59 reclaimCredit atomic.Uintptr
60
61 // arenas 是堆 arena 映射。它指向整个可用虚拟地址空间中每个 arena 帧的元数据。
62 //
63 // 使用 arenaIndex 计算该数组的索引。
64 //
65 // 对于未由 Go 堆支持的地址空间区域,arena 映射包含 nil。
66 //
67 // 修改由 mheap_.lock 保护。读取可以不加锁进行;但是,当未持有锁时,
68 // 给定条目可能随时从 nil 变为非 nil。(条目不会变回 nil。)
69 //
70 // 通常,这是一个两级映射,包括一个 L1 映射和可能的多个 L2 映射。
71 // 当有大量 arena 帧时,这节省了空间。然而,在许多平台上(即使是 64 位),
72 // arenaL1Bits 为 0,这使得它实际上是单级映射。在这种情况下,arenas[0]
73 // 永远不会是 nil。
74 arenas [1 << arenaL1Bits]*[1 << arenaL2Bits]*heapArena
75
76 // heapArenaAlloc 是用于分配 heapArena 对象的预留空间。这仅在 32 位上使用,
77 // 我们预留此空间以避免与堆本身交错。
78 heapArenaAlloc linearAlloc
79
80 // arenaHints 是一个列表,包含尝试添加更多堆 arena 的地址。最初用一组
81 // 通用提示地址填充,并随着实际堆 arena 范围的边界增长。
82 arenaHints *arenaHint
83
84 // arena 是用于分配堆 arena(实际的 arena)的预留空间。这仅在 32 位上使用。
85 arena linearAlloc
86
87 // allArenas 是所有映射的 arena 的 arenaIndex。可以用它遍历地址空间。
88 //
89 // 访问由 mheap_.lock 保护。然而,由于这是追加的,旧的底层数组永远不会被释放,
90 // 所以可以安全地获取 mheap_.lock,复制切片头,然后释放 mheap_.lock。
91 allArenas []arenaIdx
92
93 // sweepArenas 是在清扫周期开始时拍摄的 allArenas 快照。只需阻止 GC
94 // (通过禁用抢占)即可安全读取。
95 sweepArenas []arenaIdx
96
97 // markArenas 是在标记周期开始时拍摄的 allArenas 快照。由于 allArenas
98 // 仅追加,因此在标记期间此切片及其内容不会更改,因此可以安全读取。
99 markArenas []arenaIdx
100
101 // curArena 是堆当前正在扩展的 arena。应始终是 physPageSize 对齐的。
102 curArena struct {
103 base, end uintptr
104 }
105
106 _ uint32 // 确保 central 的 64 位对齐
107
108 // 小尺寸类别的中央空闲列表。
109 // padding 确保 mcentral 被 CacheLinePadSize 字节隔开,
110 // 以便每个 mcentral.lock 拥有自己的缓存行。
111 // central 由 spanClass 索引。 numSpanClasses=136
112 central [numSpanClasses]struct {
113 mcentral mcentral
114
115 // 用于内存对齐
116 pad [cpu.CacheLinePadSize - unsafe.Sizeof(mcentral{})%cpu.CacheLinePadSize]byte
117 }
118
119 spanalloc fixalloc // span* 的分配器
120 cachealloc fixalloc // mcache* 的分配器
121 specialfinalizeralloc fixalloc // specialfinalizer* 的分配器
122 specialprofilealloc fixalloc // specialprofile* 的分配器
123 specialReachableAlloc fixalloc // specialReachable 的分配器
124 speciallock mutex // special 记录分配器的锁。
125 arenaHintAlloc fixalloc // arenaHints 的分配器
126
127 unused *specialfinalizer // 从未设置,仅此处以强制将 specialfinalizer 类型放入 DWARF
128}
heapArena
Go语言中将堆划分成多个arena,在amd64的架构下,每个arena的区域大小是64MB,每个arena都对应一个heapArena相当于头,用来存储arena的元数据,
heapArena记录了页到mspan的映射关系,应为在GC的时候,通过地址便宜找页很方便,但是找到其所属的mspan则不是那么方便,所以通过映射关系来维护。
1type heapArena struct {
2 //
3 bitmap [heapArenaBitmapBytes]byte
4
5
6 spans [pagesPerArena]*mspan
7
8
9 pageInUse [pagesPerArena / 8]uint8
10
11
12 pageMarks [pagesPerArena / 8]uint8
13
14
15 pageSpecials [pagesPerArena / 8]uint8
16
17
18 checkmarks *checkmarksMap
19
20
21 zeroedBase uintptr
22}
bitmap:spans:将当前arena中的页面映射到mspan中
页索引
页索引是基于基数树实现的,结构如下:
1type pallocSum uint64
2
3// packPallocSum takes a start, max, and end value and produces a pallocSum.
4func packPallocSum(start, max, end uint) pallocSum {
5 if max == maxPackedValue {
6 return pallocSum(uint64(1 << 63))
7 }
8 return pallocSum((uint64(start) & (maxPackedValue - 1)) |
9 ((uint64(max) & (maxPackedValue - 1)) << logMaxPackedValue) |
10 ((uint64(end) & (maxPackedValue - 1)) << (2 * logMaxPackedValue)))
11}
12
13// start extracts the start value from a packed sum.
14func (p pallocSum) start() uint {
15 if uint64(p)&uint64(1<<63) != 0 {
16 return maxPackedValue
17 }
18 return uint(uint64(p) & (maxPackedValue - 1))
19}
20
21// max extracts the max value from a packed sum.
22func (p pallocSum) max() uint {
23 if uint64(p)&uint64(1<<63) != 0 {
24 return maxPackedValue
25 }
26 return uint((uint64(p) >> logMaxPackedValue) & (maxPackedValue - 1))
27}
28
29// end extracts the end value from a packed sum.
30func (p pallocSum) end() uint {
31 if uint64(p)&uint64(1<<63) != 0 {
32 return maxPackedValue
33 }
34 return uint((uint64(p) >> (2 * logMaxPackedValue)) & (maxPackedValue - 1))
35}
36
37// unpack unpacks all three values from the summary.
38func (p pallocSum) unpack() (uint, uint, uint) {
39 if uint64(p)&uint64(1<<63) != 0 {
40 return maxPackedValue, maxPackedValue, maxPackedValue
41 }
42 return uint(uint64(p) & (maxPackedValue - 1)),
43 uint((uint64(p) >> logMaxPackedValue) & (maxPackedValue - 1)),
44 uint((uint64(p) >> (2 * logMaxPackedValue)) & (maxPackedValue - 1))
45}
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步
1func mallocgc(size uintptr, typ *_type, needzero bool) unsafe.Pointer {
2 // ...
3 // 获取 m
4 mp := acquirem()
5 // 获取当前 p 对应的 mcache
6 c := getMCache(mp)
7 var span *mspan
8 var x unsafe.Pointer
9 // 根据当前对象是否包含指针,标识 gc 时是否需要展开扫描
10 noscan := typ == nil || typ.ptrdata == 0
11
12 // 是否是小于 32KB 的微、小对象
13 if size <= maxSmallSize {
14 // 小于 16 B 且无指针,则视为微对象
15 if noscan && size < maxTinySize {
16 // tiny 内存块中,从 offset 往后有空闲位置
17 off := c.tinyoffset
18 // 如果大小为 5 ~ 8 B,size 会被调整为 8 B,此时 8 & 7 == 0,会走进此分支
19 if size&7 == 0 {
20 // 将 offset 补齐到 8 B 倍数的位置
21 off = alignUp(off, 8)
22 // 如果大小为 3 ~ 4 B,size 会被调整为 4 B,此时 4 & 3 == 0,会走进此分支
23 } else if size&3 == 0 {
24 // 将 offset 补齐到 4 B 倍数的位置
25 off = alignUp(off, 4)
26 // 如果大小为 1 ~ 2 B,size 会被调整为 2 B,此时 2 & 1 == 0,会走进此分支
27 } else if size&1 == 0 {
28 // 将 offset 补齐到 2 B 倍数的位置
29 off = alignUp(off, 2)
30 }
31 // 如果当前 tiny 内存块空间还够用,则直接分配并返回
32 if off+size <= maxTinySize && c.tiny != 0 {
33 // 分配空间
34 x = unsafe.Pointer(c.tiny + off)
35 c.tinyoffset = off + size
36 c.tinyAllocs++
37 mp.mallocing = 0
38 releasem(mp)
39 return x
40 }
41 // 分配一个新的 tiny 内存块
42 span = c.alloc[tinySpanClass]
43 // 从 mCache 中获取
44 v := nextFreeFast(span)
45 if v == 0 {
46 // 从 mCache 中获取失败,则从 mCentral 或者 mHeap 中获取进行兜底
47 v, span, shouldhelpgc = c.nextFree(tinySpanClass)
48 }
49 // 分配空间
50 x = unsafe.Pointer(v)
51 (*[2]uint64)(x)[0] = 0
52 (*[2]uint64)(x)[1] = 0
53 size = maxTinySize
54 } else {
55 // 根据对象大小,映射到其所属的 span 的等级(0~66)
56 var sizeclass uint8
57 if size <= smallSizeMax-8 {
58 sizeclass = size_to_class8[divRoundUp(size, smallSizeDiv)]
59 } else {
60 sizeclass = size_to_class128[divRoundUp(size-smallSizeMax, largeSizeDiv)]
61 }
62 // 对应 span 等级下,分配给每个对象的空间大小(0~32KB)
63 size = uintptr(class_to_size[sizeclass])
64 // 创建 spanClass 标识,其中前 7 位对应为 span 的等级(0~66),最后标识表示了这个对象 gc 时是否需要扫描
65 spc := makeSpanClass(sizeclass, noscan)
66 // 获取 mcache 中的 span
67 span = c.alloc[spc]
68 // 从 mcache 的 span 中尝试获取空间
69 v := nextFreeFast(span)
70 if v == 0 {
71 // mcache 分配空间失败,则通过 mcentral、mheap 兜底
72 v, span, shouldhelpgc = c.nextFree(spc)
73 }
74 // 分配空间
75 x = unsafe.Pointer(v)
76 // ...
77 }
78 // 大于 32KB 的大对象
79 } else {
80 // 从 mheap 中获取 0 号 span
81 span = c.allocLarge(size, noscan)
82 span.freeindex = 1
83 span.allocCount = 1
84 size = span.elemsize
85 // 分配空间
86 x = unsafe.Pointer(span.base())
87 }
88 // ...
89 return x
90}