golang內存分配

jvxi1120595 8年前發布 | 11K 次閱讀 Go語言 鏈表 Google Go/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)
    ...
}

來自: http://www.cnblogs.com/yjf512/p/5147365.html

 本文由用戶 jvxi1120595 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!