硬件到软件
Go语言每次申请64MB的虚拟内存,称为heapArena
最多可以有2的20次幂个
Go语言mheap内存是由许多heapArena组成
Runtime/mheap.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| 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 }
type mheap struct { ... arenas [1 << arenaL1Bits]*[1 << arenaL2Bits]*heapArena ... }
|
上面图,我们可以看到,来1个人,可以吃,来多个人也可以吃,当然1个人占用1个包间,确实浪费空间,那就放到2人的餐桌吃饭,最多浪费1个位置,来3个人,就做4人餐位置,以此类推。
Go语言也使用了这样的思路,管理内存中的对象。
mspan内存管理单元。是由多个相同的内存单元组成。
runtime/mheap.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
| type mspan struct { next *mspan prev *mspan list *mSpanList startAddr uintptr npages uintptr manualFreeList gclinkptr nelems uintptr allocCache uint64 allocBits *gcBits gcmarkBits *gcBits sweepgen uint32 divMul uint32 allocCount uint16 spanclass spanClass state mSpanStateBox needzero uint8 allocCountBeforeCache uint16 elemsize uintptr limit uintptr speciallock mutex specials *special }
heapArena存储的是元数据。heapArena被存储在Go堆之外,并通过mheap_.arenas索引进行访问。 type heapArena struct { ... spans [pagesPerArena]*mspan ... }
|
runtime/sizeclasses.go
源代码中有1-67个级别,这里只截取了1-18级
每个heapArena(64M)里不一定包含全部的内存等级小块(mspan),Go语言会根据需要进行分配的。
Go语言如何快速找到合适的mspan
runtime/mcentral.go
1 2 3 4 5 6
| 给定大小的可用对象的中央列表。 type mcentral struct { spanclass spanClass partial [2]spanSet full [2]spanSet }
|
runtime/mheap.go
1 2 3 4 5 6 7 8 9
| mheap 不能被堆分配,因为它包含 mSpanLists. type mheap struct { central [numSpanClasses]struct { mcentral mcentral pad [cpu.CacheLinePadSize - unsafe.Sizeof(mcentral{})%cpu.CacheLinePadSize]byte } } numSpanClasses = _NumSizeClasses << 1 _NumSizeClasses = 68
|
runtime/mcache.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| 它是每个线程中小对象的缓存。它包括小型对象缓存和本地分配统计信息。无需锁定,因为它是每个线程的。 mcaches 是从非 GC'd 内存中分配的,因此任何堆指针,必须特别处理。 type mcache struct { nextSample uintptr scanAlloc uintptr tiny uintptr tinyoffset uintptr tinyAllocs uintptr alloc [numSpanClasses]*mspan // spans to allocate from, indexed by spanClass stackcache [_NumStackOrders]stackfreelist flushGen uint32 }
GMP中的P type p struct { ... mcache *mcache ... }
|
numSpanClasses就是136,每一个mcentral都对应一个spanClass。上图的mcentral可以看到也就类似一个大市场,如果大家都来读数据,没问题,但是有人读,有人写,这自然就是并发问题,要解决就要加锁,这样的话,性能就很低,基于前面的GMP模型思想,GO语言给每个P有一个mcache,先操作这个mcache,如果写满了,那么本地的mcache再和中央mcentral的进行交换,如果中央的还是满的,那么就再开一个heapArena。
Go语言对象都多大
0 < Tiny对象<16B
16B<=Small对象<=32KB
Large对象>32KB
1-67级存放 <=32KB大小的对象(Tiny对象、Small对象),可以进行多个对象进行压缩,放在一起,节省更多的空间和开销。
Large对象就是按自身大小决定,没有固定的等级。
Go语言如何对象分配
小对象是从每 P 缓存的空闲列表中分配的。
大型对象 (> 32 kB) 直接从堆中分配。
runtime/malloc.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
| func mallocgc(size uintptr, typ *_type, needzero bool) unsafe.Pointer { if size <= maxSmallSize { // maxSmallSize (32k) if noscan && size < maxTinySize {// maxTinySize 16字节 // Tiny allocator. ... // Allocate a new maxTinySize block. span = c.alloc[tinySpanClass] v := nextFreeFast(span) if v == 0 { v, span, shouldhelpgc = c.nextFree(tinySpanClass) } ... }else{ var sizeclass uint8 if size <= smallSizeMax-8 { sizeclass = size_to_class8[divRoundUp(size, smallSizeDiv)] } else { sizeclass = size_to_class128[divRoundUp(size-smallSizeMax, largeSizeDiv)] } size = uintptr(class_to_size[sizeclass]) spc := makeSpanClass(sizeclass, noscan) span = c.alloc[spc] v := nextFreeFast(span) if v == 0 { v, span, shouldhelpgc = c.nextFree(spc) } } }else{ ... span = c.allocLarge(size, noscan) } } func (c *mcache) allocLarge(size uintptr, noscan bool) *mspan { ... spc := makeSpanClass(0, noscan) s := mheap_.alloc(npages, spc) ... }
|
tiny对象
7行 tinySpanClass是2级,也就是16字节,可以通过多个tiny对象,进行压缩到这个span里
8-10行 找到一个空闲的地址,然后分配出去
small对象
14-21行 找到一个合适的span级别
22行 c是 c := getMCache(mp),然后调用alloc()
23-25行 找到合适span空间,如果找不到就去中央厂库交换调用nextFree() ->refill()
large对象
30行 创建Large对象
35行 传入0级span
Go语言的垃圾回收(GC)
那些对象是有用的
全局引用的指针
cpu正在使用的指针
图1.
图2.
图3.
图4.
删除屏障
当某个对象检查完成后,再进行删除的操作,会导致扫描错误,所以一旦有删除操作,那么就把这个对象放到灰色区域中,以后再进行分析。
插入屏障
当某个对象检查完成后,再进行插入的操作,会导致扫描错误,所以一旦有插入操作,那么就把这个对象放到灰色区域中,以后再进行分析。
如何学习Go语言微服务,快速步入架构师
添加微信 |
公众号更多内容 |
|
|