go 是常驻内存吗_golang内存分配

Go语言内置运行时(就是runtime),不同于传统的内存分配方式,go为自主管理,最开始是基于tcmalloc架构,后面逐步迭新。自主管理可实现更好的内存使用模式,如内存池、预分配等,从而避免了系统调用所带来的性能问题。

1. 基本策略

每次从操作系统申请一大块内存,然后将其按特定大小分成小块,构成链表(组织方式是一个单链表数组,数组的每个元素是一个单链表,链表中的每个元素具有相同的大小。);

为对象分配内存时从大小合适的链表提取一小块,避免每次都向操作系统申请内存,减少系统调用。

回收对象内存时将该小块重新归还到原链表,以便复用;若闲置内存过多,则归还部分内存到操作系统,降低整体开销。

1.1 内存块

span:即上面所说的操作系统分配的大块内存,由多个地址连续的页组成;

object:由span按特定大小切分的小块内存,每一个可存储一个对象;

按照用途,span面向内部管理,object面向对象分配。

关于span

内存分配器按照页数来区分不同大小的span,如以页数为单位将span存放到管理数组中,且以页数作为索引;

span大小并非不变,在没有获取到合适大小的闲置span时,返回页数更多的span,然后进行剪裁,多余的页数构成新的span,放回管理数组;

分配器还可以将相邻的空闲span合并,以构建更大的内存块,减少碎片提供更灵活的分配策略。

分配的内存块大小

在$GOROOT/src/runtime/malloc.go文件下可以找到相关信息。

1 //malloc.go

2 _PageShift = 13

3 _PageSize = 1<< _PageShift //8KB

用于存储对象的object,按8字节倍数分为n种。如,大小为24的object可存储范围在17~24字节的对象。在造成一些内存浪费的同时减少了小块内存的规格,优化了分配和复用的管理策略。

分配器还会将多个微小对象组合到一个object块内,以节约内存。

1 //malloc.go

2 _NumSizeClasses = 67

1 //mheap.go

2 type mspan struct{3 next *mspan //双向链表 next span in list, or nil if none

4 prev *mspan //previous span in list, or nil if none

5 list *mSpanList //用于调试。TODO: Remove.6

7 //起始序号 = (address >> _PageShift)

8 startAddr uintptr //address of first byte of span aka s.base()

9 npages uintptr //number of pages in span10

11 //待分配的object链表

12 manualFreeList gclinkptr //list of free objects in mSpanManual spans

13 }

分配器初始化时,会构建对照表存储大小和规格的对应关系,包括用来切分的span页数。

1 //msize.go2

3 //Malloc small size classes.4 //

5 //See malloc.go for overview.6 //See also mksizeclasses.go for how we decide what size classes to use.

7

8 package runtime9

10 //如果需要,返回mallocgc将分配的内存块的大小。

11 func roundupsize(size uintptr) uintptr {12 if size <_maxsmallsize if size smallsizemax-8 return uintptr else returnsize21 returnround _pagesize>

如果对象大小超出特定阈值限制,会被当做大对象(large object)特别对待。

1 //malloc.go

2 _MaxSmallSize = 32 << 10 //32KB

这里的对象分类:

小对象(tiny): size < 16byte;

普通对象: 16byte ~ 32K;

大对象(large):size > 32K;

1.2 内存分配器

分配器分为三个模块

cache:每个运行期工作线程都会绑定一个cache,用于无锁object分配(Central组件其实也是一个缓存,但它缓存的不是小对象内存块,而是一组一组的内存page(一个page占4k大小))。

1 //mcache.go

2 type mcache struct{3 以spanClass为索引管理多个用于分配的span4 alloc [numSpanClasses]*mspan //spans to allocate from, indexed by spanClass

5 }

central:为所有cache提供切分好的后备span资源。

1 //mcentral.go

2 type mcentral struct{3 spanclass spanClass //规格4 //链表:尚有空闲object的span

5 nonempty mSpanList //list of spans with a free object, ie a nonempty free list6 //链表:没有空闲object,或已被cache取走的span

7 empty mSpanList //list of spans with no free objects (or cached in an mcache)

8 }9

heap:管理闲置span,需要时间向操作系统申请新内存(堆分配器,以8192byte页进行管理)。

1 type mheap struct{2 largealloc uint64 //bytes allocated for large objects3 //页数大于127(>=127)的闲置span链表

4 largefree uint64 //bytes freed for large objects (>maxsmallsize)

5 nlargefree uint64 //number of frees for large objects (>maxsmallsize)6 //页数在127以内的闲置span链表数组

7 nsmallfree [_NumSizeClasses]uint64 //number of frees for small objects (<=maxsmallsize)8 //每个central对应一种sizeclass

9 central [numSpanClasses]struct{10 mcentral mcentral11 pad [cpu.CacheLinePadSize - unsafe.Sizeof(mcentral{})%cpu.CacheLinePadSize]byte

12 }

一个线程有一个cache对应,这个cache用来存放小对象。所有线程共享Central和Heap。

虚拟地址空间

内存分配和垃圾回收都依赖连续地址,所以系统预留虚拟地址空间,用于内存分配,申请内存时,系统承诺但不立即分配物理内存。虚拟地址分成三个区域:

页所属span指针数组                     spans 512MB              spans_mapped

GC标记位图                                bitmap 32GB              bit_map

用户内存分配区域                        arena  512GB              arena_start  arena_used  arena_end

三个数组组成一个高性能内存管理结构。使用arena地址向操作系统申请内存,其大小决定了可分配用户内存上限;bitmap为每个对象提供4bit 标记位,用以保存指针、GC标记等信息;创建span时,按页填充对应spans空间。这些区域的相关属性保存在heap里,其中包括递进的分配位置mapped/used。

各个模块关系图如下:

1.3 内存分配流程

从对象的角度:

1、计算待分配对象规格大小(size class);

2、cache.alloc数组中找到对应规格的apan;

3、span.freelist提取可用object,若该span.freelist为空从central获取新sapn;

4、若central.nonempty为空,从heap.free/freelarge获取,并切分成object 链表;

5、如heap没有大小合适的闲置span,向操作系统申请新内存块。

释放流程:

1、将标记为可回收的object交还给所属span.freelist;

2、该span被放回central,可供任意cache重新获取使用;

3、如span已回收全部object,则将其交还给heap,以便重新切分复用;

4、定期扫描heap里长期闲置的span,释放其占用内存。

(注:以上不包括大对象,它直接从heap分配和回收)

cache为每个工作线程私有且不被共享,是实现高性能无锁分配内存的核心。central是在多个cache中提高object的利用率,避免浪费。回收操作将span交还给central后,该span可被其他cache重新获取使用。将span归还给heap是为了在不同规格object间平衡。

2. 内存分配器初始化

初始化流程:

1 func mallocinit() {2 testdefersizes()3

4 if heapArenaBitmapBytes&(heapArenaBitmapBytes-1) != 0{5 //heapBits需要位图上的模块化算法工作地址。

6 throw("heapArenaBitmapBytes not a power of 2")7 }8

9 // //复制类大小以用于统计信息表。

10 for i :=range class_to_size {11 memstats.by_size[i].size =uint32(class_to_size[i])12 }13

14 //检查 physPageSize.

15 if physPageSize == 0{16 //操作系统初始化代码无法获取物理页面大小。

17 throw("failed to get system page size")18 }19 if physPageSize

28 //初始化堆。

29 mheap_.init()30 //为当前对象绑定cache对象

31 _g_ :=getg()32 _g_.m.mcache =allocmcache()33

34 //创建初始 arena 增长提示。

35 if sys.PtrSize == 8 && GOARCH != "wasm"{36 //在64位计算机上:37 //1.从地址空间的中间开始,可以轻松扩展到连续范围,而无需运行其他映射。38 //

39 //2.这使Go堆地址调试时更容易识别。40 //

41 //3. gccgo中的堆栈扫描仍然很保守,因此将地址与其他数据区分开很重要。42 //

43 //在AIX上,对于64位,mmaps从0x0A00000000000000开始设置保留地址,如果失败,则尝试0x1c00000000000000~0x7fc0000000000000。44 //流程.

45 for i := 0x7f; i >= 0; i--{46 varp uintptr47 switch{48 case GOARCH == "arm64" && GOOS == "darwin":49 p = uintptr(i)<<40 | uintptrMask&(0x0013<<28)50 case GOARCH == "arm64":51 p = uintptr(i)<<40 | uintptrMask&(0x0040<<32)52 case GOOS == "aix":53 if i == 0{54 //我们不会直接在0x0A00000000000000之后使用地址,以避免与非执行程序所完成的其他mmap发生冲突。

55 continue

56 }57 p = uintptr(i)<<40 | uintptrMask&(0xa0<<52)58 caseraceenabled:59 //TSAN运行时要求堆的范围为[0x00c000000000,0x00e000000000)。

60 p = uintptr(i)<<32 | uintptrMask&(0x00c0<<32)61 if p >= uintptrMask&0x00e000000000{62 continue

63 }64 default:65 p = uintptr(i)<<40 | uintptrMask&(0x00c0<<32)66 }67 hint := (*arenaHint)(mheap_.arenaHintAlloc.alloc())68 hint.addr =p69 hint.next, mheap_.arenaHints =mheap_.arenaHints, hint70 }71 } else{72 //在32位计算机上,需要更加关注保持可用堆是连续的。73 //

74 //1.我们为所有的heapArenas保留空间,这样它们就不会与heap交错。它们约为258MB。75 //

76 //2. 我们建议堆从二进制文件的末尾开始,因此我们有最大的机会保持其连续性。77 //

78 //3. 我们尝试放出一个相当大的初始堆保留。

79

80 const arenaMetaSize = (1 << arenaBits) * unsafe.Sizeof(heapArena{})81 meta :=uintptr(sysReserve(nil, arenaMetaSize))82 if meta != 0{83 mheap_.heapArenaAlloc.init(meta, arenaMetaSize)84 }85

86 procBrk :=sbrk0()87

88 p :=firstmoduledata.end89 if p

97 arenaSizes :=[]uintptr{98 512 << 20,99 256 << 20,100 128 << 20,101 }102 for _, arenaSize :=range arenaSizes {103 a, size := sysReserveAligned(unsafe.Pointer(p), arenaSize, heapArenaBytes)104 if a !=nil {105 mheap_.arena.init(uintptr(a), size)106 p = uintptr(a) + size //For hint below

107 break

108 }109 }110 hint := (*arenaHint)(mheap_.arenaHintAlloc.alloc())111 hint.addr =p112 hint.next, mheap_.arenaHints =mheap_.arenaHints, hint113 }114 }

大概流程:

1、创建对象规格大小对照表;

2、计算相关区域大小,并尝试从某个指定位置开始保留地址空间;

3、在heap里保存区域信息,包括起始位置和大小;

4、初始化heap其他属性。

看一下保留地址操作细节:

1 //mem_linux.go

2 func sysReserve(v unsafe.Pointer, n uintptr) unsafe.Pointer {3 p, err := mmap(v, n, _PROT_NONE, _MAP_ANON|_MAP_PRIVATE, -1, 0) //PORT_NONE: 页面无法访问;

4 if err != 0{5 returnnil6 }7 returnp8 }9

10 func sysMap(v unsafe.Pointer, n uintptr, sysStat *uint64) {11 mSysStatInc(sysStat, n)12

13 p, err := mmap(v, n, _PROT_READ|_PROT_WRITE, _MAP_ANON|_MAP_FIXED|_MAP_PRIVATE, -1, 0) //_MAP_FIXED: 必须使用指定起始位置

14 if err ==_ENOMEM {15 throw("runtime: out of memory")16 }17 if p != v || err != 0{18 throw("runtime: cannot map pages in arena address space")19 }20 }

函数mmap()要求操作系统内核创建新的虚拟存储器区域,可指定起始位置和长度。

3. 内存分配

编译器有责任尽可能使用寄存器和栈来存储对象,有助于提升性能,减少垃圾回收器的压力。

以new函数为例看一下内存分配

1 //test.go

2 package main3

4 import ()5

6 func test() *int{7 x :=new(int)8 *x = 0xAABB

9 returnx10 }11

12 func main(){13 println(*test())14 }

在默认有内联优化的时候:

内联优化是避免栈和抢占检查这些成本的经典优化方法。

在没有内联优化的时候new函数会调用newobject在堆上分配内存。要在两个栈帧间传递对象,因此会在堆上分配而不是返回一个失效栈帧里的数据。而当内联后它实际上就成了main栈帧内的局部变量,无须去堆上操作。

GO语言支持逃逸分析(eseape, analysis), 它会在编译期通过构建调用图来分析局部变量是否会被外部调用,从而决定是否可以直接分配在栈上。

编译参数-gcflags "-m" 可输出编译优化信息,其中包括内联和逃逸分析。性能测试时使用go-test-benchemem参数可以输出堆分配次数统计。

3.1 newobject分配内存的过程

1 //mcache.go2

3 //小对象的线程(按Go,按P)缓存。 不需要锁定,因为它是每个线程的(每个P)。 mcache是从非GC的内存中分配的,因此任何堆指针都必须进行特殊处理。4 //go:not in heap

5 type mcache struct{6 ...7 //Allocator cache for tiny objects w/o pointers.See "Tiny allocator" comm ent in malloc.go.8 //tiny指向当前微小块的开头,如果没有当前微小块,则为nil。9 //

10 //tiny是一个堆指针。 由于mcache位于非GC的内存中,因此我们通过在标记终止期间在releaseAll中将其清除来对其进行处理。

11 tiny uintptr12 tinyoffset uintptr13 local_tinyallocs uintptr //未计入其他统计信息的微小分配数14

15 //其余的不是在每个malloc上访问的。

16 alloc [numSpanClasses]*mspan //要分配的范围,由spanClass索引

17 }

内置new函数的实现

1 //malloc.go2 //implementation of new builtin3 //compiler (both frontend and SSA backend) knows the signature4 //of this function

5 func newobject(typ *_type) unsafe.Pointer {6 return mallocgc(typ.size, typ, true)7 }8

9 //Allocate an object of size bytes.10 //Small objects are allocated from the per-P cache's free lists.11 //Large objects (> 32 kB) are allocated straight from the heap.

12 ///分配一个大小为字节的对象。小对象是从per-P缓存的空闲列表中分配的。 大对象(> 32 kB)直接从堆中分配。

13 func mallocgc(size uintptr, typ *_type, needzero bool) unsafe.Pointer {14 if gcphase == _GCmarktermination { //垃圾回收有关

15 throw("mallocgc called with gcphase == _GCmarktermination")16 }17

18 if size == 0{19 return unsafe.Pointer(&zerobase)20 }21 if debug.sbrk != 0{22 align := uintptr(16)23 if typ !=nil {24 align =uintptr(typ.align)25 }26 return persistentalloc(size, align, &memstats.other_sys) //围绕sysAlloc的包装程序,可以分配小块。没有相关的自由操作。用于功能/类型/调试相关的持久数据。如果align为0,则使用默认的align(当前为8)。返回的内存将被清零。考虑将持久分配的类型标记为go:notinheap。

27 }28

29 //assistG是要为此分配收费的G,如果GC当前未激活,则为n。

30 var assistG *g31

32 ...33

34 //Set mp.mallocing to keep from being preempted by GC.35 //加锁放防止GC被抢占。

36 mp :=acquirem()37 if mp.mallocing != 0{38 throw("malloc deadlock")39 }40 if mp.gsignal ==getg() {41 throw("malloc during signal")42 }43 mp.mallocing = 1

44

45 shouldhelpgc := false

46 dataSize :=size47

48 //当前线程所绑定的cache

49 c :=gomcache()50 var x unsafe.Pointer51 //判断分配的对象是否 是nil或非指针类型

52 noscan := typ == nil || typ.kind&kindNoPointers != 0

53 //微小对象

54 if size <=maxSmallSize {55 //无须扫描非指针微小对象(16)

56 if noscan && size

61 off :=c.tinyoffset62 //对齐所需(保守)对齐的小指针。调整偏移量。

63 if size&7 == 0{64 off = round(off, 8)65 } else if size&3 == 0{66 off = round(off, 4)67 } else if size&1 == 0{68 off = round(off, 2)69 }70 //如果剩余空间足够. 当前mcache上绑定的tiny 块内存空间足够,直接分配,并返回

71 if off+size <= maxTinySize && c.tiny != 0{72 //返回指针,调整偏移量为下次分配做好准备。

73 x = unsafe.Pointer(c.tiny +off)74 c.tinyoffset = off +size75 c.local_tinyallocs++

76 mp.mallocing = 0

77 releasem(mp)78 returnx79 }80 //当前mcache上的 tiny 块内存空间不足,分配新的maxTinySize块。就是从sizeclass=2的span.freelist获取一个16字节object。

81 span :=c.alloc[tinySpanClass]82 //尝试从 allocCache 获取内存,获取不到返回0

83 v :=nextFreeFast(span)84 if v == 0{85 //没有从 allocCache 获取到内存,netxtFree函数 尝试从 mcentral获取一个新的对应规格的内存块(新span),替换原先内存空间不足的内存块,并分配内存,后面解析 nextFree 函数

86 v, _, shouldhelpgc =c.nextFree(tinySpanClass)87 }88 x = unsafe.Pointer(v)89 (*[2]uint64)(x)[0] = 0

90 (*[2]uint64)(x)[1] = 0

91 //对比新旧两个tiny块剩余空间,看看我们是否需要用剩余的自由空间来替换现有的微型块。新块分配后其tinyyoffset = size,因此比对偏移量即可

92 if size < c.tinyoffset || c.tiny == 0{93 //用新块替换

94 c.tiny =uintptr(x)95 c.tinyoffset =size96 }97 //消费一个新的完整tiny块

98 size =maxTinySize99 } else{100 //这里开始 正常对象的 内存分配101

102 //首先查表,以确定 sizeclass

103 varsizeclass uint8104 if size <= smallSizeMax-8{105 sizeclass = size_to_class8[(size+smallSizeDiv-1)/smallSizeDiv]106 } else{107 sizeclass = size_to_class128[(size-smallSizeMax+largeSizeDiv-1)/largeSizeDiv]108 }109 size =uintptr(class_to_size[sizeclass])110 spc :=makeSpanClass(sizeclass, noscan)111 //找到对应规格的span.freelist,从中提取object

112 span :=c.alloc[spc]113 //同小对象分配一样,尝试从 allocCache 获取内存,获取不到返回0

114 v :=nextFreeFast(span)115

116 //没有可用的object。从central获取新的span。

117 if v == 0{118 v, span, shouldhelpgc =c.nextFree(spc)119 }120 x = unsafe.Pointer(v)121 if needzero && span.needzero != 0{122 memclrNoHeapPointers(unsafe.Pointer(v), size)123 }124 }125 } else{126 //这里开始大对象的分配127

128 //大对象的分配与 小对象 和普通对象 的分配有点不一样,大对象直接从 mheap 上分配

129 var s *mspan130 shouldhelpgc = true

131 systemstack(func() {132 s =largeAlloc(size, needzero, noscan)133 })134 s.freeindex = 1

135 s.allocCount = 1

136 //span.start实际由address >> pageshift生成。

137 x = unsafe.Pointer(s.base())138 size =s.elemsize139 }140

141 //bitmap标记...142 //检查出发条件,启动垃圾回收 ...

143

144 returnx145 }

代码基本思路:

1. 判定对象是大对象、小对象还是微小对象。

2. 如果是微小对象:

直接从 mcache 的alloc 找到对应 classsize 的 mspan;

若当前mspan有足够空间,分配并修改mspan的相关属性(nextFreeFast函数中实现);

若当前mspan的空间不足,则从 mcentral重新获取一块 对应 classsize的 mspan,替换原先的mspan,然后分配并修改mspan的相关属性;

对于微小对象,它不能是指针,因为多个微小对象被组合到一个object里,显然无法应对辣鸡扫描。其次它从span.freelist获取一个16字节的object,然后利用偏移量来记录下一次分配的位置。

3. 如果是小对象,内存分配逻辑大致同微小对象:

首先查表,以确定 需要分配内存的对象的 sizeclass,并找到 对应 classsize的 mspan;

若当前mspan有足够的空间,分配并修改mspan的相关属性(nextFreeFast函数中实现);

若当前mspan没有足够的空间,从 mcentral重新获取一块对应 classsize的 mspan,替换原先的mspan,然后分配并修改mspan的相关属性;

4. 如果是大对象,直接从mheap进行分配,这里的实现依靠 largeAlloc 函数实现,再看一下这个函数的实现。还是在malloc.go下面:

1 func largeAlloc(size uintptr, needzero bool, noscan bool) *mspan {2 //print("largeAlloc size=", size, "\n")3

4 //内存溢出判断

5 if size+_PageSize

9 //计算出对象所需的页数

10 npages := size >>_PageShift11 if size&_PageMask != 0{12 npages++

13 }14

15 //Deduct credit for this span allocation and sweep if16 //necessary. mHeap_Alloc will also sweep npages, so this only17 //pays the debt down to npage pages.18 //清理(Sweep)垃圾

19 deductSweepCredit(npages*_PageSize, npages)20

21 //分配函数的具体实现

22 s := mheap_.alloc(npages, makeSpanClass(0, noscan), true, needzero)23 if s ==nil {24 throw("out of memory")25 }26 s.limit = s.base() +size27 //bitmap 记录分配的span

28 heapBitsForAddr(s.base()).initSpan(s)29 returns30 }

再看看 mheap_.allo()函数的实现:

1 //mheap.go2 //alloc allocates a new span of npage pages from the GC'd heap.3 //Either large must be true or spanclass must indicates the span's size class and scannability.4 //If needzero is true, the memory for the returned span will be zeroed.

5 func (h *mheap) alloc(npage uintptr, spanclass spanClass, large bool, needzero bool) *mspan {6 //Don't do any operations that lock the heap on the G stack.7 //It might trigger stack growth, and the stack growth code needs8 //to be able to allocate heap.9 //如果needzero为true,则返回范围的内存将为零。10 //不要执行任何将堆锁定在G堆栈上的操作。11 //它可能会触发堆栈增长,而堆栈增长代码需要能够分配堆。

12 var s *mspan13 systemstack(func() {14 s =h.alloc_m(npage, spanclass, large)15 })16

17 if s !=nil {18 if needzero && s.needzero != 0{19 memclrNoHeapPointers(unsafe.Pointer(s.base()), s.npages<<_pageshift s.needzero="0</p">

22 }23 returns24 }

mheap.alloc_m()根据页数从 heap 上面分配一个新的span,并且在 HeapMap 和 HeapMapCache 上记录对象的sizeclass。

1 //mheap.go

2 func (h *mheap) alloc_m(npage uintptr, spanclass spanClass, large bool) *mspan {3 _g_ :=getg()4 if _g_ !=_g_.m.g0 {5 throw("_mheap_alloc not on g0 stack")6 }7 lock(&h.lock)8

9 //清理垃圾,内存块状态标记 省略...10

11 //从 heap中获取指定页数的span

12 s := h.allocSpanLocked(npage, &memstats.heap_inuse)13 if s !=nil {14 //Record span info, because gc needs to be15 //able to map interior pointer to containing span.

16 atomic.Store(&s.sweepgen, h.sweepgen)17 h.sweepSpans[h.sweepgen/2%2].push(s) //Add to swept in-use list.//忽略

18 s.state =_MSpanInUse19 s.allocCount = 0

20 s.spanclass =spanclass21 //重置span的状态

22 if sizeclass := spanclass.sizeclass(); sizeclass == 0{23 s.elemsize = s.npages <<_pageshift24 s.divshift="0</p">

25 s.divMul = 0

26 s.divShift2 = 0

27 s.baseMask = 0

28 } else{29 s.elemsize =uintptr(class_to_size[sizeclass])30 m := &class_to_divmagic[sizeclass]31 s.divShift =m.shift32 s.divMul =m.mul33 s.divShift2 =m.shift234 s.baseMask =m.baseMask35 }36

37 //update stats, sweep lists

38 h.pagesInUse +=uint64(npage)39 iflarge {40 //更新 mheap中大对象的相关属性

41 memstats.heap_objects++

42 mheap_.largealloc +=uint64(s.elemsize)43 mheap_.nlargealloc++

44 atomic.Xadd64(&memstats.heap_live, int64(npage<<_pageshift spans are at the end of lists.46 busylarge>

47 if s.npages

55 unlock(&h.lock)56 returns57 }

mheap.allocSpanLocked()函数分配一个给定大小的span,并将分配的span从freelist中移除。

//mheap.go

1 func (h *mheap) allocSpanLocked(npage uintptr, stat *uint64) *mspan {2 var list *mSpanList3 var s *mspan4

5 //Try in fixed-size lists up to max.6 //先尝试获取指定页数的span,如果没有,则试试页数更多的

7 for i := int(npage); i < len(h.free); i++{8 list = &h.free[i]9 if !list.isEmpty() {10 s =list.first11 list.remove(s)12 gotoHaveSpan13 }14 }15 //Best fit in list of large spans.16 //从 freelarge 上找到一个合适的span节点返回 ,下面继续分析这个函数

17 s = h.allocLarge(npage) //allocLarge removed s from h.freelarge for us

18 if s ==nil {19 //如果 freelarge上找不到合适的span节点,就只有从 系统 重新分配了20 //后面继续分析这个函数

21 if !h.grow(npage) {22 returnnil23 }24 //从系统分配后,再次到freelarge 上寻找合适的节点

25 s =h.allocLarge(npage)26 if s ==nil {27 returnnil28 }29 }30

31 HaveSpan:32 //从 free 上面获取到了 合适页数的span33 //Mark span in use. 省略....

34

35 if s.npages >npage {36 //Trim extra and put it back in the heap.37 //创建一个 s.napges - npage 大小的span,并放回 heap

38 t := (*mspan)(h.spanalloc.alloc())39 t.init(s.base()+npage<<_pageshift s.npages-npage s>

41 s.npages =npage42 h.setSpan(t.base()-1, s)43 h.setSpan(t.base(), t)44 h.setSpan(t.base()+t.npages*pageSize-1, t)45 t.needzero =s.needzero46 s.state = _MSpanManual //prevent coalescing with s

47 t.state =_MSpanManual48 h.freeSpanLocked(t, false, false, s.unusedsince)49 s.state =_MSpanFree50 }51 s.unusedsince = 0

52 //将s放到spans 和 arenas 数组里面

53 h.setSpans(s.base(), npage, s)54

55 *stat += uint64(npage <<_pageshift memstats.heap_idle uint64>

58 //println("spanalloc", hex(s.start<<_pageshift>

59 ifs.inList() {60 throw("still in list")61 }62 returns63 }

mheap.allocLarge()函数从 mheap 的 freeLarge 树上面找到一个指定page数量的span,并将该span从树上移除,找不到则返回nil。

1 //mheap.go

2 func (h *mheap) allocLarge(npage uintptr) *mspan {3 //Search treap for smallest span with >= npage pages.

4 returnh.freelarge.remove(npage)5 }6

7 //上面的 h.freelarge.remove 即调用这个函数8 //典型的二叉树寻找算法

9 func (root *mTreap) remove(npages uintptr) *mspan {10 t :=root.treap11 for t !=nil {12 if t.spanKey ==nil {13 throw("treap node with nil spanKey found")14 }15 if t.npagesKey =npages {18 t =t.left19 } else{20 result :=t.spanKey21 root.removeNode(t)22 returnresult23 }24 }25 returnnil26 }

mheap.grow()函数在 mheap.allocSpanLocked 这个函数中,如果 freelarge上找不到合适的span节点,就只有从系统重新分配了,那我们接下来就继续分析一下这个函数的实现。

1 func (h *mheap) grow(npage uintptr) bool{2 ask := npage <<_pageshift3 sysalloc>

4 v, size :=h.sysAlloc(ask)5 if v ==nil {6 print("runtime: out of memory: cannot allocate", ask, "-byte block (", memstats.heap_sys, "in use)\n")7 return false

8 }9

10 //Create a fake "in use" span and free it, so that the11 //right coalescing happens.12 //创建 span 来管理刚刚申请的内存

13 s := (*mspan)(h.spanalloc.alloc())14 s.init(uintptr(v), size/pageSize)15 h.setSpans(s.base(), s.npages, s)16 atomic.Store(&s.sweepgen, h.sweepgen)17 s.state =_MSpanInUse18 h.pagesInUse +=uint64(s.npages)19 //将刚刚申请的span放到 arenas 和 spans 数组里面

20 h.freeSpanLocked(s, false, true, 0)21 return true

22 }

mheao.sysAlloc()

1 func (h *mheap) sysAlloc(n uintptr) (v unsafe.Pointer, size uintptr) {2 n =round(n, heapArenaBytes)3

4 //First, try the arena pre-reservation.5 //从 arena 中 获取对应大小的内存, 获取不到返回nil

6 v = h.arena.alloc(n, heapArenaBytes, &memstats.heap_sys)7 if v !=nil {8 //从arena获取到需要的内存,跳转到 mapped操作

9 size =n10 gotomapped11 }12

13 //Try to grow the heap at a hint address.14 //尝试 从 arenaHint向下扩展内存

15 for h.arenaHints !=nil {16 hint :=h.arenaHints17 p :=hint.addr18 ifhint.down {19 p -=n20 }21 if p+n

24 v =nil25 } else if arenaIndex(p+n-1) >= 1<

28 v =nil29 } else{30 //当前hint可以向下扩展内存,利用mmap向系统申请内存

31 v = sysReserve(unsafe.Pointer(p), n)32 }33 if p ==uintptr(v) {34 //Success. Update the hint.

35 if !hint.down {36 p +=n37 }38 hint.addr =p39 size =n40 break

41 }42 //Failed. Discard this hint and try the next.43 //

44 //TODO: This would be cleaner if sysReserve could be45 //told to only return the requested address. In46 //particular, this is already how Windows behaves, so47 //it would simply things there.

48 if v !=nil {49 sysFree(v, n, nil)50 }51 h.arenaHints =hint.next52 h.arenaHintAlloc.free(unsafe.Pointer(hint))53 }54

55 if size == 0{56 ifraceenabled {57 //The race detector assumes the heap lives in58 //[0x00c000000000, 0x00e000000000), but we59 //just ran out of hints in this region. Give60 //a nice failure.

61 throw("too many address space collisions for -race mode")62 }63

64 //All of the hints failed, so we'll take any65 //(sufficiently aligned) address the kernel will give66 //us.

67 v, size =sysReserveAligned(nil, n, heapArenaBytes)68 if v ==nil {69 return nil, 0

70 }71

72 //Create new hints for extending this region.

73 hint := (*arenaHint)(h.arenaHintAlloc.alloc())74 hint.addr, hint.down = uintptr(v), true

75 hint.next, mheap_.arenaHints =mheap_.arenaHints, hint76 hint = (*arenaHint)(h.arenaHintAlloc.alloc())77 hint.addr = uintptr(v) +size78 hint.next, mheap_.arenaHints =mheap_.arenaHints, hint79 }80

81 //Check for bad pointers or pointers we can't use.

82 {83 var bad string

84 p :=uintptr(v)85 if p+size

87 } else if arenaIndex(p) >= 1<

89 } else if arenaIndex(p+size-1) >= 1<

91 }92 if bad != ""{93 //This should be impossible on most architectures,94 //but it would be really confusing to debug.

95 print("runtime: memory allocated by OS [", hex(p), ",", hex(p+size), ") not in usable address space:", bad, "\n")96 throw("memory reservation exceeds address space limit")97 }98 }99

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

104 //Back the reservation.

105 sysMap(v, size, &memstats.heap_sys)106

107 mapped:108 //Create arena metadata.109 //根据 v 的address,计算出 arenas 的L1 L2

110 for ri := arenaIndex(uintptr(v)); ri <= arenaIndex(uintptr(v)+size-1); ri++{111 l2 :=h.arenas[ri.l1()]112 if l2 ==nil {113 //如果 L2 为 nil,则分配 arenas[L1]114 //Allocate an L2 arena map.

115 l2 = (*[1 << arenaL2Bits]*heapArena)(persistentalloc(unsafe.Sizeof(*l2), sys.PtrSize, nil))116 if l2 ==nil {117 throw("out of memory allocating heap arena map")118 }119 atomic.StorepNoWB(unsafe.Pointer(&h.arenas[ri.l1()]), unsafe.Pointer(l2))120 }121

122 //如果 arenas[ri.L1()][ri.L2()] 不为空 说明已经实例化过了

123 if l2[ri.l2()] !=nil {124 throw("arena already initialized")125 }126 var r *heapArena127 //从 arena 上分配内存

128 r = (*heapArena)(h.heapArenaAlloc.alloc(unsafe.Sizeof(*r), sys.PtrSize, &memstats.gc_sys))129 if r ==nil {130 r = (*heapArena)(persistentalloc(unsafe.Sizeof(*r), sys.PtrSize, &memstats.gc_sys))131 if r ==nil {132 throw("out of memory allocating heap arena metadata")133 }134 }135

136 //Store atomically just in case an object from the137 //new heap arena becomes visible before the heap lock138 //is released (which shouldn't happen, but there's139 //little downside to this).

140 atomic.StorepNoWB(unsafe.Pointer(&l2[ri.l2()]), unsafe.Pointer(r))141 }142 //...

143 return

144 }

大对象的分配流程至此结束。

3.2 小对象和微小对象的分配

nextFreeFast()函数返回 span 上可用的地址,如果找不到 则返回0

1 func nextFreeFast(s *mspan) gclinkptr {2 //计算s.allocCache从低位起有多少个0

3 theBit := sys.Ctz64(s.allocCache) //Is there a free object in the allocCache?

4 if theBit < 64{5

6 result := s.freeindex +uintptr(theBit)7 if result

9 if freeidx%64 == 0 && freeidx !=s.nelems {10 return 0

11 }12 //更新bitmap、可用的 slot索引

13 s.allocCache >>= uint(theBit + 1)14 s.freeindex =freeidx15 s.allocCount++

16 //返回 找到的内存的地址

17 return gclinkptr(result*s.elemsize + s.base())18 }19 }20 return 0

21 }

mcache.nextFree()函数。如果 nextFreeFast 找不到 合适的内存,就会进入这个函数。nextFree 如果在cached span 里面找到未使用的object,则返回,否则,调用refill 函数,从 central 中获取对应classsize的span,然后 从新的span里面找到未使用的object返回。

1 //mcache.go

2 func (c *mcache) nextFree(spc spanClass) (v gclinkptr, s *mspan, shouldhelpgc bool) {3 //先找到 mcache 中 对应 规格的 span

4 s =c.alloc[spc]5 shouldhelpgc = false

6 //在 当前span中找到合适的 index索引

7 freeIndex :=s.nextFreeIndex()8 if freeIndex ==s.nelems {9 //The span is full.10 //freeIndex == nelems 时,表示当前span已满

11 if uintptr(s.allocCount) !=s.nelems {12 println("runtime: s.allocCount=", s.allocCount, "s.nelems=", s.nelems)13 throw("s.allocCount != s.nelems && freeIndex == s.nelems")14 }15 //调用refill函数,从 mcentral 中获取可用的span,并替换掉当前 mcache里面的span

16 systemstack(func() {17 c.refill(spc)18 })19 shouldhelpgc = true

20 s =c.alloc[spc]21

22 //再次到新的span里面查找合适的index

23 freeIndex =s.nextFreeIndex()24 }25

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

30 //计算出来 内存地址,并更新span的属性

31 v = gclinkptr(freeIndex*s.elemsize + s.base())32 s.allocCount++

33 if uintptr(s.allocCount) >s.nelems {34 println("s.allocCount=", s.allocCount, "s.nelems=", s.nelems)35 throw("s.allocCount > s.nelems")36 }37 return

38 }

mcache.refill()函数

Refill 根据指定的sizeclass获取对应的span,并作为 mcache的新的sizeclass对应的span

1 //mcache.go

2 func (c *mcache) refill(spc spanClass) {3 _g_ :=getg()4

5 _g_.m.locks++

6 //Return the current cached span to the central lists.

7 s :=c.alloc[spc]8

9 if uintptr(s.allocCount) !=s.nelems {10 throw("refill of span with free space remaining")11 }12

13 //判断s是不是 空的span

14 if s != &emptymspan {15 s.incache = false

16 }17 //尝试从 mcentral 获取一个新的span来代替老的span18 //Get a new cached span from the central lists.

19 s =mheap_.central[spc].mcentral.cacheSpan()20 if s ==nil {21 throw("out of memory")22 }23

24 if uintptr(s.allocCount) ==s.nelems {25 throw("span has no free space")26 }27 //更新mcache的span

28 c.alloc[spc] =s29 _g_.m.locks--

30 }

如果 从 mcentral 找不到对应的span,就会开始内存扩张,和上面分析的 mheap.alloc就相同了

4. 总结

1. 判定对象大小:

2. 若是微小对象:

从 mcache 的 alloc 找到对应 classsize 的 mspan;

当前mspan有足够的空间时,分配并修改mspan的相关属性(nextFreeFast函数中实现);

若当前mspan没有足够的空间,从 mcentral 重新获取一块对应 classsize的 mspan,替换原先的mspan,然后分配并修改mspan的相关属性;

若 mcentral 没有足够的对应的classsize的span,则去向mheap申请;

若对应classsize的span没有了,则找一个相近的classsize的span,切割并分配;

若找不到相近的classsize的span,则去向系统申请,并补充到mheap中;

3. 若是小对象,内存分配逻辑大致同小对象:

查表以确定需要分配内存的对象的 sizeclass,找到 对应classsize的 mspan;

mspan有足够的空间时,分配并修改mspan的相关属性(nextFreeFast函数中实现);

若当前mspan没有足够的空间,从 mcentral重新获取一块对应 classsize的 mspan,替换原先的mspan,然后分配并修改mspan的相关属性;

若mcentral没有足够的对应的classsize的span,则去向mheap申请;

若对应classsize的span没有了,则找一个相近的classsize的span,切割并分配

若找不到相近的classsize的span,则去向系统申请,并补充到mheap中

4. 若是大对象,直接从mheap进行分配

若对应classsize的span没有了,则找一个相近的classsize的span,切割并分配;

若找不到相近的classsize的span,则去向系统申请,并补充到mheap中;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值