Go語言內(nèi)存分配詳解:mcache與mheap的協(xié)同工作

2025-01-15 10:28 更新

大家好,我是V 哥。GO GO GO,今天來說一說Go語言內(nèi)存分配問題,Go語言內(nèi)存分配的源碼主要集中在runtime包中,它實(shí)現(xiàn)了Go語言的內(nèi)存管理,包括初始化、分配、回收和釋放等。下面來對這些過程詳細(xì)分析一下,先贊后看,絕不擺爛:

1. 內(nèi)存管理初始化

源碼位置: runtime/malloc.go

關(guān)鍵點(diǎn):

  • mheap初始化:
    • mheap是整個Go運(yùn)行時的核心內(nèi)存分配結(jié)構(gòu),用于管理大塊內(nèi)存。
    • 初始化時,Go會從操作系統(tǒng)中獲取一大塊內(nèi)存作為堆空間,通過sysAlloc分配給mheap。

  func mallocinit() {
      mheap_.init() // 初始化全局mheap_
  }

  • mcache初始化:
    • 每個P(邏輯處理器)有一個mcache,用來緩存小塊內(nèi)存分配,減少鎖競爭。
    • mcachemheap分配,存儲小塊內(nèi)存(≤32KB)。

  func allocmcache() *mcache {
      c := new(mcache)
      c.refill() // 預(yù)填充小內(nèi)存塊
      return c
  }

2. 內(nèi)存分配

Go的內(nèi)存分配分為以下幾種場景:

2.1 小對象分配(≤32KB)

  • 使用mcache中的內(nèi)存。
  • mcache按大小類(class)分配,這些類通過sizeclasses數(shù)組定義。
  • 分配時,調(diào)用mcache.alloc,如果mcache中沒有可用的內(nèi)存塊,會從mheap中拉取。

  func mallocgc(size uintptr, typ *_type, needzero bool) unsafe.Pointer {
      if size <= maxSmallSize {
          // 小對象分配
          c := getmcache() // 獲取當(dāng)前P的mcache
          s := c.alloc(size, needzero)
          return s
      }
  }

2.2 大對象分配(>32KB)

  • 直接從mheap中分配大塊內(nèi)存。
  • 使用span(連續(xù)內(nèi)存塊)管理這些大塊內(nèi)存。

  func mallocgc(size uintptr, typ *_type, needzero bool) unsafe.Pointer {
      if size > maxSmallSize {
          // 大對象分配
          s := mheap_.allocSpan(size)
          return s
      }
  }

3. 垃圾回收(GC)

源碼位置: runtime/mgc.go

Go的垃圾回收器使用三色標(biāo)記清除算法,主要分以下幾個階段:

  1. 標(biāo)記階段:
    • 從根對象(全局變量、棧變量、寄存器變量)開始,標(biāo)記所有可達(dá)對象。
  2. 清除階段:
    • 將未標(biāo)記的對象回收,釋放到mcachemheap。

   func gcSweep() {
       // 遍歷所有span,清理未使用的對象
       for _, s := range mheap_.spans {
           s.sweep()
       }
   }

4. 內(nèi)存釋放

源碼位置: runtime/malloc.go

Go會主動將不再使用的大塊內(nèi)存返還給操作系統(tǒng),調(diào)用sysUnusedsysFree實(shí)現(xiàn)。

關(guān)鍵點(diǎn):

  • 小對象:
    • 釋放到mcache。
    • 如果mcache滿了,釋放到mheap。
  • 大對象:
    • 直接釋放到mheap。
    • 如果mheap中內(nèi)存長時間未使用,釋放給操作系統(tǒng)。

5. 內(nèi)存分配中的優(yōu)化機(jī)制

5.1 線程本地緩存(mcache)

  • 減少全局鎖競爭。
  • 小對象分配從mcache中直接獲取。

5.2 內(nèi)存對齊

  • Go保證分配的內(nèi)存地址按對象大小對齊(如8字節(jié)、16字節(jié)等),以提高訪問效率。

5.3 分配池(Free List)

  • 回收的內(nèi)存會進(jìn)入Free List,供后續(xù)快速分配。

5.4 GC觸發(fā)條件

  • 當(dāng)堆的增長超過特定比例(默認(rèn)100%)時觸發(fā)GC。

Go的內(nèi)存分配機(jī)制結(jié)合了現(xiàn)代內(nèi)存分配的多種優(yōu)化技術(shù),能夠高效地處理并發(fā)場景。關(guān)鍵點(diǎn)在于:

  • 小對象通過mcache優(yōu)化分配速度。
  • 大對象通過mheap管理,提高內(nèi)存利用率。
  • 垃圾回收器負(fù)責(zé)自動清理無用內(nèi)存,保證程序健壯性。
  • 同時,內(nèi)存釋放機(jī)制及時將多余內(nèi)存返還給操作系統(tǒng),避免浪費(fèi)。

6. mcache 和 mheap

深入分析 Go 內(nèi)存管理中核心模塊 mcachemheap 的代碼實(shí)現(xiàn),可以更好地理解它們的協(xié)同工作方式。以下是詳細(xì)的源碼分析:

1. mcache 模塊

1.1 mcache 數(shù)據(jù)結(jié)構(gòu)

mcache 是每個 P (邏輯處理器) 的本地內(nèi)存緩存,目的是減少對全局堆的鎖爭用。
它的源碼定義在 runtime/mcache.go。

type mcache struct {
    alloc [numSpanClasses]*mspan // 每個 size class 分配一個 span
    tiny        uintptr          // 小對象分配緩存
    tinyoffset  uintptr          // tiny 的當(dāng)前偏移量
    local_nlookup uintptr        // 本地分配次數(shù)
    ...
}

字段解釋:

  • alloc:
    • 存儲分配的 spans,按 size class 分類。
    • 每個類的 span 會被重用以分配同類大小的對象。
  • tinytinyoffset:
    • 用于小對象分配(如 mallocgc)。
  • local_nlookup:
    • 用于統(tǒng)計(jì)本地內(nèi)存分配的次數(shù)。

1.2 mcache 的主要方法

1.2.1 分配內(nèi)存 (mcache.alloc)

當(dāng)分配小對象時,調(diào)用 alloc 方法從 mcache 中獲取內(nèi)存:

func (c *mcache) alloc(size uintptr, needzero bool) unsafe.Pointer {
    sc := sizeToClass(size) // 根據(jù) size 找到對應(yīng)的 size class
    s := c.alloc[sc]
    if s == nil || s.freeindex == s.nelems { 
        // 當(dāng)前緩存中沒有可用的 span,從 mheap 中獲取
        s = mheap_.allocSpan(sc)
        if s == nil {
            throw("out of memory")
        }
        c.alloc[sc] = s
    }
    ...
    return obj
}

工作流程:

  1. 根據(jù) size 計(jì)算 size class。
  2. 查找對應(yīng)的 span:
    • 如果 span 有空閑塊,從 freeindex 取一個。
    • 如果 span 已滿,從 mheap 中分配新的 span。
  3. 返回分配的對象地址。

1.2.2 釋放內(nèi)存 (mcache.releaseAll)

當(dāng) GC 發(fā)生時,mcache 會將所有未使用的 spans 返還給 mheap。

func (c *mcache) releaseAll() {
    for i := range c.alloc {
        s := c.alloc[i]
        if s != nil {
            mheap_.freeSpan(s) // 釋放到 mheap
            c.alloc[i] = nil
        }
    }
}

2. mheap 模塊

2.1 mheap 數(shù)據(jù)結(jié)構(gòu)

mheap 是全局的堆管理器,負(fù)責(zé)分配和回收大塊內(nèi)存(span),以及為 mcache 提供支持。它的源碼定義在 runtime/mheap.go。

type mheap struct {
    spans []*mspan        // 全局管理的 spans
    freelist [numSpanClasses]*mspan // 每個 size class 的空閑列表
    arenas [maxArenas]*heapArena // 內(nèi)存分配的區(qū)域
    lock mutex           // 全局鎖
    ...
}

字段解釋:

  • spans:
    • 按頁索引管理的所有 spans。
  • freelist:
    • 每個 size class 的空閑 span 鏈表。
  • arenas:
    • 堆內(nèi)存分配的底層區(qū)域,映射到操作系統(tǒng)的物理內(nèi)存。
  • lock:
    • 對全局堆操作加鎖,避免并發(fā)問題。

2.2 mheap 的主要方法

2.2.1 分配 span (mheap.allocSpan)

當(dāng) mcache 需要新的 span 時,會調(diào)用 mheap.allocSpan

func (h *mheap) allocSpan(sc spanClass) *mspan {
    lock(&h.lock) // 加鎖,防止并發(fā)沖突
    s := h.freelist[sc]
    if s != nil {
        h.freelist[sc] = s.next // 從 freelist 獲取 span
        unlock(&h.lock)
        return s
    }
    ...
    unlock(&h.lock)
    return h.grow(sc) // freelist 沒有時,從 arenas 擴(kuò)展
}

工作流程:

  1. freelist 中取出一個空閑的 span。
  2. 如果 freelist 為空,調(diào)用 grow 方法,從 arenas 分配新的 span。

2.2.2 回收 span (mheap.freeSpan)

當(dāng) mcache 或垃圾回收器釋放內(nèi)存時,調(diào)用 mheap.freeSpan

func (h *mheap) freeSpan(s *mspan) {
    lock(&h.lock) // 加鎖
    sc := s.spanclass()
    s.reset() // 重置 span 狀態(tài)
    s.next = h.freelist[sc]
    h.freelist[sc] = s // 回收到 freelist
    unlock(&h.lock)
}

工作流程:

  1. 通過 spanclass 確定 span 類型。
  2. 重置 span 的元數(shù)據(jù)。
  3. 將 span 加入 freelist 鏈表。

2.3 mheap 內(nèi)存增長 (mheap.grow)

當(dāng) freelist 無法滿足分配請求時,從底層 arenas 分配新的 span:

func (h *mheap) grow(sc spanClass) *mspan {
    p := sysAlloc(_PageSize * npage, &memstats.heap_sys) // 從操作系統(tǒng)分配物理內(nèi)存
    if p == nil {
        throw("out of memory")
    }
    s := newMSpan() // 創(chuàng)建新的 span
    s.init(p, npage) 
    ...
    return s
}

3. mcache 與 mheap 的協(xié)作流程

  1. 分配內(nèi)存:
    • 小對象: 先從 mcache 中分配。
    • 大對象: 直接通過 mheap 分配。
  2. 回收內(nèi)存:
    • mcache 釋放的內(nèi)存會回收到 mheap,進(jìn)入 freelist。
  3. GC 的作用:
    • 清理未使用的對象。
    • 調(diào)用 mcache.releaseAllmheap.freeSpan 釋放無用的 span。

4. 小結(jié)一下

  • mcache 是每個 P (邏輯處理器) 的本地緩存,優(yōu)化小對象分配的性能。
  • mheap 是全局堆管理器,負(fù)責(zé)大對象分配和全局內(nèi)存回收。
  • 兩者通過 span 的共享和回收機(jī)制協(xié)作,兼顧性能與內(nèi)存利用率。
  • 垃圾回收器(GC)在這個體系中扮演清理者的角色,保證內(nèi)存的高效使用。

關(guān)注威哥愛編程,成長路上一起努力,點(diǎn)個贊再走唄。

以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號