golang內存分配
golang內存分配
new一個對象的時候,入口函數是malloc.go中的newobject函數
func newobject(typ *_type) unsafe.Pointer { flags := uint32(0) if typ.kind&kindNoPointers != 0 { flags |= flagNoScan } return mallocgc(uintptr(typ.size), typ, flags) }
這個函數先計算出傳入參數的大小,然后調用mallocgc函數,這個函數三個參數,第一個參數是對象類型大小,第二個參數是對象類型,第三個參數是malloc的標志位,這個標志位有兩位,一個標志位代表GC不需要掃描這個對象,另一個標志位說明這個對象并不是空內存
const ( // flags to malloc _FlagNoScan = 1 << 0 // GC doesn't have to scan object _FlagNoZero = 1 << 1 // don't zero memory )
mallocgc函數定義如下:
func mallocgc(size uintptr, typ *_type, flags uint32) unsafe.Pointer
它返回的是指向這個結構的指針。進入看里面的方法
先是會進行下面的操作
// 基本的條件符合判斷 ... // 獲取當前goroutine的m結構 mp := acquirem() // 如果當前的m正在執行分配任務,則拋出錯誤 if mp.mallocing != 0 { throw("malloc deadlock") } if mp.gsignal == getg() { throw("malloc during signal") } // 鎖住當前的m進行分配 mp.mallocing = 1 shouldhelpgc := false dataSize := size // 獲取當前goroutine的m的mcache c := gomcache() var s *mspan var x unsafe.Pointer
其中的m,p,g的信息需要對下面這個圖有印象
然后根據size判斷是否是大對象,小對象,微小對象
如果是微小對象:
// 是微小對象 // 進行微小對象的校準操作 // ... // 如果是微小對象,并且申請的對象微小對象能cover住 if off+size <= maxTinySize && c.tiny != nil { // 直接在tiny的塊中進行分配就行了 x = add(c.tiny, off) ... return x } // 從mcache中獲取對應的span鏈表 s = c.alloc[tinySizeClass] v := s.freelist // 如果這個span鏈表沒有微小對象的空閑span了,從MCache中獲取tinySize的鏈表補充上這個tiny鏈表 if v.ptr() == nil { systemstack(func() { mCache_Refill(c, tinySizeClass) }) } s.freelist = v.ptr().next s.ref++ // 預讀取指令能加快速度 prefetchnta(uintptr(v.ptr().next)) // 初始化微小結構 x = unsafe.Pointer(v) (*[2]uint64)(x)[0] = 0 (*[2]uint64)(x)[1] = 0 // 對比新舊兩個tiny塊剩余空間 if size < c.tinyoffset { // 如果舊塊的剩余空間比新塊少,則使用新塊替代mcache中的tiny塊 c.tiny = x c.tinyoffset = size }
如果是小對象
// 是小對象 var sizeclass int8 // 計算最接近的size if size <= 1024-8 { sizeclass = size_to_class8[(size+7)>>3] } else { sizeclass = size_to_class128[(size-1024+127)>>7] } size = uintptr(class_to_size[sizeclass]) // 獲取mcache中預先分配的spans鏈表 s = c.alloc[sizeclass] v := s.freelist if v.ptr() == nil { // 如果沒有鏈表了,則從mcache中劃出對應的spans鏈表 systemstack(func() { mCache_Refill(c, int32(sizeclass)) }) } // 有鏈表則直接使用 s.freelist = v.ptr().next s.ref++
如果是大對象,則直接從heap上拿內存
// 如果是大對象,直接去heap中獲取數據 systemstack(func() { s = largeAlloc(size, uint32(flags)) }) x = unsafe.Pointer(uintptr(s.start << pageShift)) size = uintptr(s.elemsize)
總結一下
- 如果要申請的對象是tiny大小,看mcache中的tiny block是否足夠,如果足夠,直接分配。如果不足夠,使用mcache中的tiny class對應的span分配
- 如果要申請的對象是小對象大小,則使用mcache中的對應span鏈表分配
- 如果對應span鏈表已經沒有空span了,先補充上mcache的對應鏈表,再分配(mCache_Refill)
- 如果要申請的對象是大對象,直接去heap中獲取(largeAlloc)
再仔細看代碼,不管是tiny大小的對象還是小對象,他們去mcache中獲取對象都是使用mCache_Refill方法為這個對象對應的鏈表申請內存。那么我們可以追到里面去看看。
func mCache_Refill(c *mcache, sizeclass int32) *mspan { // 獲取當時的goroutine _g_ := getg() // 鎖上m _g_.m.locks++ // 獲取對應sizeclass的span鏈表,如果對應的鏈表還有剩余空間,拋出錯誤 s := c.alloc[sizeclass] if s.freelist.ptr() != nil { throw("refill on a nonempty span") } // 從mCentral中獲取span鏈表,并賦值 s = mCentral_CacheSpan(&mheap_.central[sizeclass].mcentral) c.alloc[sizeclass] = s // 打開鎖 _g_.m.locks-- return s }
這里實際是使用mCentral_CacheSpan來獲取內存,這里需要看下mCentral的結構
type mcentral struct { lock mutex sizeclass int32 nonempty mspan // list of spans with a free object empty mspan // list of spans with no free objects (or cached in an mcache) }
mcentral有兩個鏈表,一個鏈表是有空閑的span可以使用,叫noempty,另一個鏈表是沒有空間的span可以使用,叫empty。這個時候我們需要獲取span,一定是從nonempty鏈表中取出span來使用。
這兩個鏈表的機制是這樣的,我new一個對象的時候,從nonempty中獲取這個空間,放到empty鏈表中去,當我free一個對象的時候,從empty鏈表中還原到nonempty鏈表中去。
所以在下面獲取空span的時候,會先去empty中查找有沒有,如果沒有,再去nonempty中查找有沒有,nonempty中有可能有為資源回收但是卻是沒有使用的span。
func mCentral_CacheSpan(c *mcentral) *mspan { sg := mheap_.sweepgen retry: var s *mspan // 遍歷有空間span的鏈表 for s = c.nonempty.next; s != &c.nonempty; s = s.next { // 如果這個span是需要回收的,那么先回收這個span,轉移到empty鏈表中,再把這個span返回 if s.sweepgen == sg-2 && cas(&s.sweepgen, sg-2, sg-1) { mSpanList_Remove(s) mSpanList_InsertBack(&c.empty, s) unlock(&c.lock) // 垃圾清理 mSpan_Sweep(s, true) goto havespan } // 如果nonempty中有不需要swapping的空間,這個就可以直接使用了 mSpanList_Remove(s) mSpanList_InsertBack(&c.empty, s) unlock(&c.lock) goto havespan } // 遍歷沒有空間的span鏈表,為什么沒有空間的span鏈表也需要遍歷呢? for s = c.empty.next; s != &c.empty; s = s.next { // 如果這個span是需要回收的,回收之 if s.sweepgen == sg-2 && cas(&s.sweepgen, sg-2, sg-1) { mSpanList_Remove(s) mSpanList_InsertBack(&c.empty, s) unlock(&c.lock) mSpan_Sweep(s, true) if s.freelist.ptr() != nil { goto havespan } lock(&c.lock) goto retry } break } unlock(&c.lock) // 到這里就說明central中都沒有可以使用的span了,那么,就增長mCentral s = mCentral_Grow(c) mSpanList_InsertBack(&c.empty, s) havespan: // 找到空span的情況 cap := int32((s.npages << _PageShift) / s.elemsize) n := cap - int32(s.ref) if n == 0 { throw("empty span") } if s.freelist.ptr() == nil { throw("freelist empty") } s.incache = true return s }
mCentral判斷一個span是否過期是使用
s.sweepgen == sg-2 && cas(&s.sweepgen, sg-2, sg-1)
這個sweepgen是span和mheap中各有一個,根據這兩個結構的sweepgen就能判斷這個span是否需要進入gc回收了。
// sweep generation: // if sweepgen == h->sweepgen - 2, the span needs sweeping // if sweepgen == h->sweepgen - 1, the span is currently being swept // if sweepgen == h->sweepgen, the span is swept and ready to use // h->sweepgen is incremented by 2 after every GC
如果mCentral沒有可用的span了,就需要調用mCentral_Grow(c)
func mCentral_Grow(c *mcentral) *mspan { ... // 從heap上進行分配 s := mHeap_Alloc(&mheap_, npages, c.sizeclass, false, true) ... // 設置span的bitmap heapBitsForSpan(s.base()).initSpan(s.layout()) return s }
再進入到mHeap_Alloc
func mHeap_Alloc(h *mheap, npage uintptr, sizeclass int32, large bool, needzero bool) *mspan { ... systemstack(func() { s = mHeap_Alloc_m(h, npage, sizeclass, large) }) ... }
再進入mHeap_Alloc_m
func mHeap_Alloc_m(h *mheap, npage uintptr, sizeclass int32, large bool) *mspan { ... s := mHeap_AllocSpanLocked(h, npage) ... return s }
func mHeap_AllocSpanLocked(h *mheap, npage uintptr) *mspan { ... // 獲取Heap中最合適的內存大小 s = mHeap_AllocLarge(h, npage) // 如果mHeap滿了 if s == nil { // 增長mHeap大小 if !mHeap_Grow(h, npage) { return nil } s = mHeap_AllocLarge(h, npage) if s == nil { return nil } } HaveSpan: // mHeap中有了數據 }
看看如何增長mHeap大小
func mHeap_Grow(h *mheap, npage uintptr) bool { ... // 調用操作系統分配內存 v := mHeap_SysAlloc(h, ask) ... }
下面就看到mheap的擴容了,這個之前需要了解heap的結構
type mheap struct { lock mutex free [_MaxMHeapList]mspan // free lists of given length freelarge mspan // free lists length >= _MaxMHeapList busy [_MaxMHeapList]mspan // busy lists of large objects of given length busylarge mspan // busy lists of large objects length >= _MaxMHeapList allspans **mspan // all spans out there gcspans **mspan // copy of allspans referenced by gc marker or sweeper nspan uint32 sweepgen uint32 // sweep generation, see comment in mspan sweepdone uint32 // all spans are swept // span lookup spans **mspan spans_mapped uintptr // Proportional sweep spanBytesAlloc uint64 // bytes of spans allocated this cycle; updated atomically pagesSwept uint64 // pages swept this cycle; updated atomically sweepPagesPerByte float64 // proportional sweep ratio; written with lock, read without // Malloc stats. largefree uint64 // bytes freed for large objects (>maxsmallsize) nlargefree uint64 // number of frees for large objects (>maxsmallsize) nsmallfree [_NumSizeClasses]uint64 // number of frees for small objects (<=maxsmallsize) // range of addresses we might see in the heap bitmap uintptr bitmap_mapped uintptr arena_start uintptr arena_used uintptr // always mHeap_Map{Bits,Spans} before updating arena_end uintptr arena_reserved bool // central free lists for small size classes. // the padding makes sure that the MCentrals are // spaced CacheLineSize bytes apart, so that each MCentral.lock // gets its own cache line. central [_NumSizeClasses]struct { mcentral mcentral pad [_CacheLineSize]byte } spanalloc fixalloc // allocator for span* cachealloc fixalloc // allocator for mcache* specialfinalizeralloc fixalloc // allocator for specialfinalizer* specialprofilealloc fixalloc // allocator for specialprofile* speciallock mutex // lock for special record allocators. }
它最重要的結構有三個,spans,指向所有span指針,bitmap是spans的標志位,arena是堆生成區。
+---------------------+---------------+-----------------------------+ | spans 512MB .......| bitmap 32GB | arena 512GB ..................| +---------------------+---------------+-----------------------------+ +
func mHeap_SysAlloc(h *mheap, n uintptr) unsafe.Pointer { // 如果超出了arean預留的區塊限制了 if n > uintptr(h.arena_end)-uintptr(h.arena_used) { // 使用一些系統保留的空間 ... } // 申請的大小在arean范圍內 if n <= uintptr(h.arena_end)-uintptr(h.arena_used) { // 使用系統的sysMap申請內存 sysMap((unsafe.Pointer)(p), n, h.arena_reserved, &memstats.heap_sys) mHeap_MapBits(h, p+n) mHeap_MapSpans(h, p+n) ... } ... }
func sysMap(v unsafe.Pointer, n uintptr, reserved bool, sysStat *uint64) { ... // 最終調用mmap p := mmap(v, n, _PROT_READ|_PROT_WRITE, _MAP_ANON|_MAP_FIXED|_MAP_PRIVATE, -1, 0) ... }