欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Golang內(nèi)存管理之內(nèi)存分配器詳解

 更新時(shí)間:2023年06月30日 11:36:20   作者:IguoChan  
Go內(nèi)存分配器的設(shè)計(jì)思想來(lái)源于TCMalloc,全稱(chēng)是Thread-Caching?Malloc,核心思想是把內(nèi)存分為多級(jí)管理,下面就來(lái)和大家深入聊聊Go語(yǔ)言?xún)?nèi)存分配器的使用吧

0. 簡(jiǎn)介

程序中的數(shù)據(jù)都會(huì)被分配到程序所在的虛擬內(nèi)存中,內(nèi)存空間包含兩個(gè)重要區(qū)域:棧(Stack)堆(Heap)。函數(shù)調(diào)用的參數(shù)、返回值和局部變量大部分會(huì)分配在棧上,這部分由編譯器管理。堆內(nèi)存的管理方式視語(yǔ)言而定:

  • C/C++等編程語(yǔ)言的堆內(nèi)存由工程師主動(dòng)申請(qǐng)和釋放;
  • Go、Java等編程語(yǔ)言由工程師和編譯器/運(yùn)行時(shí)共同管理,其內(nèi)存由內(nèi)存分配器分配,由垃圾回收器回收。

本文就介紹一下Go語(yǔ)言的內(nèi)存分配器。

1. Go內(nèi)存分配設(shè)計(jì)原理

Go內(nèi)存分配器的設(shè)計(jì)思想來(lái)源于TCMalloc,全稱(chēng)是Thread-Caching Malloc,核心思想是把內(nèi)存分為多級(jí)管理,利用緩存的思想提升內(nèi)存使用效率,降低鎖的粒度。

在堆內(nèi)存管理上分為三個(gè)內(nèi)存級(jí)別:

  • 線(xiàn)程緩存(MCache):作為線(xiàn)程獨(dú)立的內(nèi)存池,與線(xiàn)程的第一交互內(nèi)存,訪(fǎng)問(wèn)無(wú)需加鎖;
  • 中心緩存(MCentral):作為線(xiàn)程緩存的下一級(jí),是多個(gè)線(xiàn)程共享的,所以訪(fǎng)問(wèn)時(shí)需要加鎖;
  • 頁(yè)堆(MHeap):中心緩存的下一級(jí),在遇到32KB以上的對(duì)象時(shí),會(huì)直接選擇頁(yè)堆分配大內(nèi)存,而當(dāng)頁(yè)堆內(nèi)存不夠時(shí),則會(huì)通過(guò)系統(tǒng)調(diào)用向系統(tǒng)申請(qǐng)內(nèi)存。

1.1 內(nèi)存管理基本單元mspan

//go:notinheap
type mspan struct {
   next *mspan     // next span in list, or nil if none
   prev *mspan     // previous span in list, or nil if none
   list *mSpanList // For debugging. TODO: Remove.
   startAddr uintptr // address of first byte of span aka s.base()
   npages    uintptr // number of pages in span
   freeindex uintptr
   allocBits  *gcBits
   gcmarkBits *gcBits
   allocCache uint64
   ...
}

runtime.mspan是Go內(nèi)存管理的基本單元,其結(jié)構(gòu)體中包含的nextprev指針,分別指向前后的runtime.mspan,所以其串聯(lián)后的結(jié)構(gòu)是一個(gè)雙向鏈表。

startAddr表示此mspan的起始地址,npages表示管理的頁(yè)數(shù),每頁(yè)大小8KB,這個(gè)頁(yè)不是操作系統(tǒng)的內(nèi)存頁(yè),一般是操作系統(tǒng)內(nèi)存頁(yè)的整數(shù)倍。

其它字段:

  • freeindex — 掃描頁(yè)中空閑對(duì)象的初始索引;
  • allocBits 和 gcmarkBits — 分別用于標(biāo)記內(nèi)存的占用和回收情況;
  • allocCache — allocBits 的補(bǔ)碼,可以用于快速查找內(nèi)存中未被使用的內(nèi)存;

注意使用//go:notinheap標(biāo)記次結(jié)構(gòu)體mspan為非堆上類(lèi)型,保證此類(lèi)型對(duì)象不會(huì)逃逸到堆上。

圖示:

跨度類(lèi)

mspan中有一個(gè)字段是spanclass,稱(chēng)為跨度類(lèi),是對(duì)mspan大小級(jí)別的劃分,每個(gè)mspan能夠存放指定范圍大小的對(duì)象,32KB以?xún)?nèi)的小對(duì)象在Go中,會(huì)對(duì)應(yīng)不同大小的內(nèi)存刻度Size Class,Size Class和Object Size是一一對(duì)應(yīng)的,前者指序號(hào) 0、1、2、3,后者指具體對(duì)象大小 0B、8B、16B、24B

//go:notinheap
type mspan struct {
   ...
   spanclass   spanClass     // size class and noscan (uint8)
   ...
}

Go 語(yǔ)言的內(nèi)存管理模塊中一共包含 67 種跨度類(lèi),每一個(gè)跨度類(lèi)都會(huì)存儲(chǔ)特定大小的對(duì)象并且包含特定數(shù)量的頁(yè)數(shù)以及對(duì)象,所有的數(shù)據(jù)都會(huì)被預(yù)選計(jì)算好并存儲(chǔ)在runtime.class_to_sizeruntime.class_to_allocnpages等變量中:

classbytes/objbytes/spanobjectstail wastemax waste
1881921024087.50%
2168192512043.75%
3248192341029.24%
4328192256046.88%
54881921703231.52%
6648192128023.44%
78081921023219.07%
6732768327681012.50%

上表展示了對(duì)象大小從 8B 到 32KB,總共 67 種跨度類(lèi)的大小、存儲(chǔ)的對(duì)象數(shù)以及浪費(fèi)的內(nèi)存空間,以表中的第四個(gè)跨度類(lèi)為例,跨度類(lèi)為 5 的runtime.mspan中對(duì)象的大小上限為 48 字節(jié)、管理 1 個(gè)頁(yè)、最多可以存儲(chǔ) 170 個(gè)對(duì)象。因?yàn)閮?nèi)存需要按照頁(yè)進(jìn)行管理,所以在尾部會(huì)浪費(fèi) 32 字節(jié)的內(nèi)存,當(dāng)頁(yè)中存儲(chǔ)的對(duì)象都是 33 字節(jié)時(shí),最多會(huì)浪費(fèi) 31.52% 的資源:

((48−33)∗170+32)/8192=0.31518

除了上述 67 個(gè)跨度類(lèi)之外,運(yùn)行時(shí)中還包含 ID 為 0 的特殊跨度類(lèi),它能夠管理大于 32KB 的特殊對(duì)象。

1.2 線(xiàn)程緩存(mcache)

runtime.mcache是Go語(yǔ)言中的線(xiàn)程緩存,它會(huì)與線(xiàn)程上的處理器意義綁定,用于緩存用戶(hù)程序申請(qǐng)的微小對(duì)象。每一個(gè)線(xiàn)程緩存都持有numSpanClasses個(gè)(68∗2)個(gè)mspan,存儲(chǔ)在mcachealloc字段中:

//go:notinheap
type mcache struct {
   ...
   alloc [numSpanClasses]*mspan // spans to allocate from, indexed by spanClass
   ...
}

1.3 中心緩存(mcentral)

每個(gè)中心緩存都會(huì)管理某個(gè)跨度類(lèi)的內(nèi)存管理單元,它會(huì)同時(shí)持有兩個(gè)runtime.spanSet,分別存儲(chǔ)包含空閑對(duì)象和不包含空閑對(duì)象的內(nèi)存管理單元,訪(fǎng)問(wèn)中心緩存中的內(nèi)存管理單元需要使用互斥鎖。

如圖上所示,是 runtime.mcentral 中的 spanSet 的內(nèi)存結(jié)構(gòu),index 字段是一個(gè)uint64類(lèi)型數(shù)字的地址,該uint64的數(shù)字按32位分為前后兩半部分head和tail,向spanSet中插入和獲取mspan有其提供的push和pop函數(shù),以push函數(shù)為例,會(huì)根據(jù)index的head,對(duì)spanSetBlock數(shù)據(jù)塊包含的mspan的個(gè)數(shù)512取商,得到spanSetBlock數(shù)據(jù)塊所在的地址,然后head對(duì)512取余,得到要插入的mspan在該spanSetBlock數(shù)據(jù)塊的具體地址。之所以是512,因?yàn)閟panSet指向的spanSetBlock數(shù)據(jù)塊是一個(gè)包含512個(gè)mspan的集合。

由全部spanClass規(guī)格的runtime.mcentral共同組成的緩存結(jié)構(gòu)如下:

1.4 頁(yè)堆(mheap)

//go:notinheap
type mheap struct {
   ...
   arenas [1 << arenaL1Bits]*[1 << arenaL2Bits]*heapArena
   ...
   central [numSpanClasses]struct {
      mcentral mcentral
      pad      [cpu.CacheLinePadSize - unsafe.Sizeof(mcentral{})%cpu.CacheLinePadSize]byte
   }
   ...
}

runtime.mheap是內(nèi)存分配的核心結(jié)構(gòu)體,其最重要的兩個(gè)字段如上。

在Go中其被作為全局變量mheap_存儲(chǔ):

var mheap_ mheap

頁(yè)堆中包含一個(gè)長(zhǎng)度為numSpanClasses個(gè)(68∗2)個(gè)的runtime.mcentral數(shù)組,其中 68 個(gè)為跨度類(lèi)需要 scan 的中心緩存,另外的 68 個(gè)是 noscan (沒(méi)有指針,無(wú)需掃描)的中心緩存。

arenas是heapArena的二維數(shù)組的集合。如下:

2. 內(nèi)存分配

堆上所有的對(duì)象內(nèi)存分配都會(huì)通過(guò)runtime.newobject進(jìn)行分配,運(yùn)行時(shí)根據(jù)對(duì)象大小將它們分為微對(duì)象、小對(duì)象和大對(duì)象:

  • 微對(duì)象(0, 16B):先使用微型分配器,再依次嘗試線(xiàn)程緩存、中心緩存和堆分配內(nèi)存;多個(gè)小于16B的無(wú)指針微對(duì)象的內(nèi)存分配請(qǐng)求,會(huì)合并向Tiny微對(duì)象空間申請(qǐng),微對(duì)象的 16B 內(nèi)存空間從 spanClass 為 4 或 5(無(wú)GC掃描)的mspan中獲取。
  • 小對(duì)象[16B, 32KB]:先向mcache申請(qǐng),mcache內(nèi)存空間不夠時(shí),向mcentral申請(qǐng),mcentral不夠,則向頁(yè)堆mheap申請(qǐng),再不夠就向操作系統(tǒng)申請(qǐng)。
  • 大對(duì)象(32KB, +∞):大對(duì)象直接向頁(yè)堆mheap申請(qǐng)。

對(duì)于內(nèi)存的釋放,遵循逐級(jí)釋放的策略。當(dāng)ThreadCache的緩存充足或者過(guò)多時(shí),則會(huì)將內(nèi)存退還給CentralCache。當(dāng)CentralCache內(nèi)存過(guò)多或者充足,則將低命中內(nèi)存塊退還PageHeap。

以上就是Golang內(nèi)存管理之內(nèi)存分配器詳解的詳細(xì)內(nèi)容,更多關(guān)于Golang內(nèi)存分配器的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • 自動(dòng)生成代碼controller?tool的簡(jiǎn)單使用

    自動(dòng)生成代碼controller?tool的簡(jiǎn)單使用

    這篇文章主要為大家介紹了自動(dòng)生成代碼controller?tool的簡(jiǎn)單使用示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-05-05
  • C語(yǔ)言的10大基礎(chǔ)算法

    C語(yǔ)言的10大基礎(chǔ)算法

    算法是一個(gè)程序和軟件的靈魂,作為一名優(yōu)秀的程序員,只有對(duì)一些基礎(chǔ)的算法有著全面的掌握,才會(huì)在設(shè)計(jì)程序和編寫(xiě)代碼的過(guò)程中顯得得心應(yīng)手。這篇文章主要介紹了C語(yǔ)言的10大基礎(chǔ)算法,需要的朋友可以參考下
    2019-09-09
  • golang解析json數(shù)據(jù)的4種方法總結(jié)

    golang解析json數(shù)據(jù)的4種方法總結(jié)

    在日常工作中每一名開(kāi)發(fā)者,不管是前端還是后端,都經(jīng)常使用 JSON,下面這篇文章主要給大家介紹了關(guān)于golang解析json數(shù)據(jù)的4種方法,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2023-06-06
  • go循環(huán)依賴(lài)的最佳解決方案

    go循環(huán)依賴(lài)的最佳解決方案

    ? import cycle not allowed(循環(huán)依賴(lài)不被允許)相信作為每一個(gè)golang語(yǔ)言使用研發(fā),都遇到過(guò)這個(gè)令人頭痛的報(bào)錯(cuò),循環(huán)依賴(lài)是指兩個(gè)或多個(gè)模塊之間互相依賴(lài),形成了一個(gè)閉環(huán)的情況,本文會(huì)結(jié)合部分案例對(duì)解決方案進(jìn)行講解,需要的朋友可以參考下
    2023-10-10
  • Go語(yǔ)言中sync.Cond使用詳解

    Go語(yǔ)言中sync.Cond使用詳解

    本文主要介紹了Go語(yǔ)言中sync.Cond使用詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-09-09
  • 詳解Golang中創(chuàng)建error的方式總結(jié)與應(yīng)用場(chǎng)景

    詳解Golang中創(chuàng)建error的方式總結(jié)與應(yīng)用場(chǎng)景

    Golang中創(chuàng)建error的方式包括errors.New、fmt.Errorf、自定義實(shí)現(xiàn)了error接口的類(lèi)型等,本文主要為大家介紹了這些方式的具體應(yīng)用場(chǎng)景,需要的可以參考一下
    2023-07-07
  • Go語(yǔ)言錯(cuò)誤處理異常捕獲+異常拋出

    Go語(yǔ)言錯(cuò)誤處理異常捕獲+異常拋出

    這篇文章主要介紹了Go語(yǔ)言錯(cuò)誤處理異常捕獲和異常拋出,Go語(yǔ)言的作者認(rèn)為java等語(yǔ)言的錯(cuò)誤處理底層實(shí)現(xiàn)較為復(fù)雜,就實(shí)現(xiàn)了函數(shù)可以返回錯(cuò)誤類(lèi)型以及簡(jiǎn)單的異常捕獲,雖然簡(jiǎn)單但是也非常精妙,大大的提高了運(yùn)行效率,下文需要的朋友可以參考一下
    2022-02-02
  • Golang之sync.Pool使用詳解

    Golang之sync.Pool使用詳解

    這篇文章主要介紹了Golang之sync.Pool使用詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2021-05-05
  • go項(xiàng)目打包部署的完整步驟

    go項(xiàng)目打包部署的完整步驟

    之前斷斷續(xù)續(xù)的接觸到項(xiàng)目部署,一直沒(méi)有詳細(xì)的了解部署,于是最近就好好的專(zhuān)研一下項(xiàng)目的部署,下面這篇文章主要給大家介紹了關(guān)于go項(xiàng)目打包部署的相關(guān)資料,需要的朋友可以參考下
    2022-09-09
  • 基于GORM實(shí)現(xiàn)CreateOrUpdate方法詳解

    基于GORM實(shí)現(xiàn)CreateOrUpdate方法詳解

    這篇文章主要為大家介紹了基于GORM實(shí)現(xiàn)CreateOrUpdate方法詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-10-10

最新評(píng)論