go 記憶體管理

来源:https://www.cnblogs.com/studyios/archive/2023/12/03/17873578.html
-Advertisement-
Play Games

人吧,不能糊裡糊塗的瞎忙活,你得規劃,成為體系後,方能事半功倍。 道、法、術、器:出自老子的《道德經》 道,是規則、自然法則,上乘。 法,是方法、法理,中乘。 術,是行為、方式,下乘。“以道御術”即以道義來承載智術,悟道比修煉法術更高一籌。“術”要符合“法”,“法”要基於“道”,道法術三者兼備才能做 ...


協程棧

go 棧的位置

  1. Go 協程棧位於 Go-堆記憶體上

  2. Go 堆記憶體位於操作系統虛擬記憶體上

go 棧的工作流程

以main.main為出發點

  1. 要記錄runtime.main的棧基地址
  2. 記錄 a 和 b的局部變數值
  3. 開闢一個空間記錄 sum函數的返回值
  4. 記錄 b 和 a的值, 這裡是為了方便 sum在執行時候,去找這個傳入的參數
  5. 記錄 sum返回後,要執行的指令,就是 print的執行位置.

這裡面能有一個重要體現, a 和 b 的值被覆制了一份, 一般稱為拷貝傳遞或值傳遞

傳遞結構體時:會拷貝結構體中的全部內容.

傳遞結構體指針時:會拷貝結構體指針

小結 協程棧記錄的內容:

  協程的執行路徑

  局部變數

  函數傳參

  函數返回值

問題: 如果協程棧的空間不夠大了, 該怎麼處理?

  本地變數太大:比如有個變數 map 存了特別多的數據.

  棧幀太多 :比如 函數a調用函數b, 一層層套,過多了.

棧空間不足

本地變數太大 使用逃逸分析

棧幀太多  使用 棧擴容

逃逸分析

針對變數太大, go的解決辦法 在編譯階段進行逃逸分析. 不是所有的變數都能放在協程棧上

需要逃逸的變數:

棧幀回收後,需要繼續使用的變數

太大的變數

指針逃逸

 好理解, 函數a返回一個局部變數 b的地址給 函數A,
 這種情況在很多語言中是會出問題的,返回了一個局部變數地址, 使用時候會發生無法預料的結果.
 
但是go會做逃逸分析, 會把這個變數放到堆上去.

空介面逃逸

  如果函數參數為 interfacef, 函數的實參很可能會逃逸,例如 fmt包中的列印.

  因為 interfacef類型的函數往往會使用反射

大變數逃逸

  過大的變數會導致棧空間不足

  64 位機器中,一般超過 64KB 的變數會逃逸

棧擴容

  Go 棧的初始空間為 2KB

  在函數調用前判斷棧空間(morestack) , 這個方法在講 切換協程時候, 這個方法也是一個時機

  必要時對棧進行擴容

  早期使用分段棧,後期使用連續棧

分段棧

每調用一個函數, 就跳轉到一塊寫的棧空間.

連續棧

優點:空間一直連續

缺點:伸縮時的開銷大

當空間不足時擴容,變為原來的2倍

當空間使用率不足 1/4 時縮容,變為原來的 1/2

協程棧的工作流程大致如上, 但是, 不管是棧還是堆上, 這些 記憶體是如何管理, 如何分配給申請的變數的 ?

go 記憶體管理

操作系統的虛擬記憶體

不是Win 的"虛擬記憶體〞, 不是指 操作系統將 硬碟的一塊空間 當做虛擬記憶體使用.

操作系統給應用提供的虛擬記憶體空間

背後是物理記憶體,也有可能有磁碟

Linux 獲取虛擬記憶體:mmap 或 madvice
假設機器的物理記憶體只有64G, 但是通過操作系統的虛擬記憶體擴大, 所有的進程都會以為擁有了一
塊特別大的空間, 256T, 但是不能真正用到這麼多, 一旦用太多就會被系統幹掉. Linux中的OOM.

這塊是操作系統的事, go 只能合理使用操作系統給進程的記憶體.

heapArena

Go 每次申請的虛擬記憶體單元為 64MB , 就是 heapArena, 如果每次拿的太小, 會多次去申請, 影響效率.

最多有4,194,304 個虛擬記憶體單元 (2的20次方) 剛好256T

所有的 heapArena 組成了mheap(Go 堆記憶體)

抽象出了兩個概念 :

整個go的堆記憶體 mheap , 包含了很多  heapArena

代碼定義:

// 代表著 整個go的記憶體空間
type mheap struct {
	_ sys.NotInHeap
	lock mutex
	pages pageAlloc // page allocation data structure
	sweepgen uint32 
	allspans []*mspan // all spans out there
	pagesInUse         atomic.Uintptr // pages of spans in stats mSpanInUse
	pagesSwept         atomic.Uint64  // pages swept this cycle
	pagesSweptBasis    atomic.Uint64  // pagesSwept to use as the origin of the sweep ratio
	sweepHeapLiveBasis uint64         // value of gcController.heapLive to use as the origin of sweep ratio; written with lock, read without
	sweepPagesPerByte  float64        // proportional sweep ratio; written with lock, read without
	reclaimIndex atomic.Uint64
	reclaimCredit atomic.Uintptr
       //  包含 最多 2的20次方個 heapArena 
	arenas [1 << arenaL1Bits]*[1 << arenaL2Bits]*heapArena 
	arenasHugePages bool
	heapArenaAlloc linearAlloc
	arenaHints *arenaHint
	arena linearAlloc
	allArenas []arenaIdx
	sweepArenas []arenaIdx
	markArenas []arenaIdx
	curArena struct {
		base, end uintptr
	}
	central [numSpanClasses]struct {
		mcentral mcentral
		pad      [(cpu.CacheLinePadSize - unsafe.Sizeof(mcentral{})%cpu.CacheLinePadSize) % cpu.CacheLinePadSize]byte
	}
	spanalloc              fixalloc // allocator for span*
	cachealloc             fixalloc // allocator for mcache*
	specialfinalizeralloc  fixalloc // allocator for specialfinalizer*
	specialprofilealloc    fixalloc // allocator for specialprofile*
	specialReachableAlloc  fixalloc // allocator for specialReachable
	specialPinCounterAlloc fixalloc // allocator for specialPinCounter
	speciallock            mutex    // lock for special record allocators.
	arenaHintAlloc         fixalloc // allocator for arenaHints
	// User arena state.
	// Protected by mheap_.lock.
	userArena struct {
		arenaHints *arenaHint
		quarantineList mSpanList
		readyList mSpanList
	}
	unused *specialfinalizer // never set, just here to force the specialfinalizer type into DWARF
}

// 代表64M的記憶體空間
type heapArena struct {
	_ sys.NotInHeap
	bitmap [heapArenaBitmapWords]uintptr
	noMorePtrs [heapArenaBitmapWords / 8]uint8
	spans [pagesPerArena]*mspan  // mspan 下麵會出現
	pageInUse [pagesPerArena / 8]uint8
	pageMarks [pagesPerArena / 8]uint8
	pageSpecials [pagesPerArena / 8]uint8
	checkmarks *checkmarksMap
	zeroedBase uintptr
}

heapArena 分配方式

為了更好的管理這些申請的記憶體空間, heapArena會把這些空間 分成 大小不一的記憶體塊,進行管理. 一共 67種. 這些同級別的 一組記憶體塊就是 mspan 大小8k.

因此,go裡面一共有 67種 mspan.

每組 都是 8K, 因為 單個的大小不同,所以 分配的個數也不同.

所以, 協程在申請空間時候, 分配的是mspan中的一個對象, 一個對象代表著一個級別的記憶體大小,比如1級的8個位元組的空間

申請完64M的空間後,也不是馬上把給個級別都分配一次,而是按需分配,比如現在需要1級的,那就只分配一組一級的, 可能最後這64M裡面只有一級的

type mspan struct {
	_    sys.NotInHeap
	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
	manualFreeList gclinkptr // list of free objects in mSpanManual spans
	freeindex uintptr
	nelems uintptr // number of object in the span.
	allocCache uint64
	allocBits  *gcBits
	gcmarkBits *gcBits
	pinnerBits *gcBits // bitmap for pinned objects; accessed atomically
	sweepgen              uint32
	divMul                uint32        // for divide by elemsize
	allocCount            uint16        // number of allocated objects
	spanclass             spanClass     // size class and noscan (uint8)
	state                 mSpanStateBox // mSpanInUse etc; accessed atomically (get/set methods)
	needzero              uint8         // needs to be zeroed before allocation
	isUserArenaChunk      bool          // whether or not this span represents a user arena
	allocCountBeforeCache uint16        // a copy of allocCount that is stored just before this span is cached
	elemsize              uintptr       // computed from sizeclass or from npages
	limit                 uintptr       // end of data in span
	speciallock           mutex         // guards specials list and changes to pinnerBits
	specials              *special      // linked list of special records sorted by offset.
	userArenaChunkFree    addrRange     // interval for managing chunk allocation
	freeIndexForScan uintptr
}


    next *mspan     // next span in list, or nil if none
	prev *mspan     // previous span in list, or nil if none
    這兩個欄位說明,mspan 可以形成一個鏈表. 

spanclass             spanClass     // 是pan的級別 1到67

這樣的結構,導致一個問題, 很多不同級別的mspan在不同的 heapArena中, 當去協程去申請時候, 比如申請一個 一級的 span, 如何去尋找 ?

需要一個 索引管理 .

中心索引mcentral

136個mcentral結構體,其中 68個組需要GC掃描的mspan, 68個組不需要GC掃描的mspan(例如存放一些常量)

每一組對應一個級別的mspan, mspan本身是可以組成一個鏈表的 nextprev.

// Central list of free objects of a given size.
type mcentral struct {
	_         sys.NotInHeap
	spanclass spanClass // 一個具體的級別
	partial [2]spanSet // list of spans with a free object  空閑的
	full    [2]spanSet // list of spans with no free objects  不空閑
}

mheap結構體定義的: 
numSpanClasses = 136
central [numSpanClasses]struct {
		mcentral mcentral
		pad      [(cpu.CacheLinePadSize - unsafe.Sizeof(mcentral{})%cpu.CacheLinePadSize) % cpu.CacheLinePadSize]byte
	}

從mheap出發, 通過這個 central 這個元素 , 能找到所有的mspan

小結和問題:

  mcentral實際是中心素引,使用互斥鎖保護

  在高併發場景下,鎖衝突問題嚴重

  參考協程GMP模型,增加線程本地緩存

線程緩存 mcache

模仿GMP的本地協程隊列, 避免每次申請都去 mcentral中, 因為 要為了線程安全, mcentral 需要加鎖, 導致有性能問題.

給每一個p增加一些 span的緩存,這樣避免每次都去申請.

每個P擁有一個mcache,也就是每個M,每個線程;

一個mcache擁有136個mspan,其中68個需要GC掃描的mspan, 68個不需要GC掃描的mspan
一樣來一個

如何本地的span滿了, 就需要去和mcentral中的span 進行交換, 如果mcentral中也沒有可用的,就去申請,通過mheapArena申請一個64M的空間

代碼定義:

numSpanClasses =136
type mcache struct {
	_ sys.NotInHeap
	// The following members are accessed on every malloc,
	// so they are grouped here for better caching.
	nextSample uintptr // trigger heap sample after allocating this many bytes
	scanAlloc  uintptr // bytes of scannable heap allocated
	tiny       uintptr
	tinyoffset uintptr
	tinyAllocs uintptr
    // 包含了136個span
	alloc [numSpanClasses]*mspan // spans to allocate from, indexed by spanClass

	stackcache [_NumStackOrders]stackfreelist
	flushGen atomic.Uint32
}

// 部分欄位
type p struct {
	id          int32
	m           muintptr   // back-link to associated m (nil if idle)
	mcache      *mcache //對應一個mcahe
}

小結:

Go模仿TCmalloc (C++使用,也是Google開發的),建立了自己的堆記憶體架構
使用heapArena向操作系統申請記憶體
使用heapArena時,以mspan為單位,防止碎片化
mcentral是mspan們的中心索引
mcache記錄了分配給各個P的本地mspan

如何分配這些記憶體

對象分級

  Tiny 微對象(0,16B) 無指針  如果是結構體, 成員不能包含指針
  Small 小對象 [16B,32KB]
  Large 大對象 (32KB, +∞)

使用

  微小對象分配至普通 mspan, 32k以下

  大對象量身定做 mspan, 這裡值得是0級, 上面提到mspan有67級, 
  但是,在定義mcentral時候, 定了68個不需要GC的,還有一個是沒有固定大小的級別, 0級.

微對象分配

  1. 從mcache 拿到2級mspan
  2. 將多個微對象合併成一個16Byte 存入

大對象分配

直接從heapArena開闢0級的mspan
0級的mspan為大對象定製

在runtime的 malloc.go中能體現:

// 分配記憶體的入口
  func mallocgc(size uintptr, typ *_type, needzero bool) unsafe.Pointer {
  	
  	if size == 0 {
  		return unsafe.Pointer(&zerobase)
  	}
     if size <= maxSmallSize {
      // 微小對象分配
      } else {
        span = c.allocLarge(size, noscan)
      }
  }

// allocLarge allocates a span for a large object.
func (c *mcache) allocLarge(size uintptr, noscan bool) *mspan {
	npages := size >> _PageShift
	if size&_PageMask != 0 {
		npages++
	}
	deductSweepCredit(npages*_PageSize, npages)
	spc := makeSpanClass(0, noscan) // 0級 class
	s := mheap_.alloc(npages, spc)
	if s == nil {
		throw("out of memory")
	}
}

分配規則小結

Go將對象按照大小分為3種
微小對象使用mcache
mcache中的mspan填滿後,與mcentral交換新的
mcentral不足時,在heapArena開闢新的mspan
大對象直接在heapArena開闢新的mspan

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 模塊--》文件 包--》文件夾 我們將類似功能的模塊放到一起(包)內,要用時直接導入即可 語法: import 模塊名 as 別名: 》取別名是為了可以節省時間,簡化代碼 import win32process as pro # 以後直接用pro即可 模塊分為三種: 1.內置模塊 2.第三方模塊 3 ...
  • SQL處理日期 在資料庫操作中,處理日期是一個關鍵的方面。確保插入的日期格式與資料庫中日期列的格式匹配至關重要。以下是一些常見的SQL日期數據類型和處理方法。 SQL日期數據類型 MySQL日期數據類型 DATE - 格式為YYYY-MM-DD DATETIME - 格式為YYYY-MM-DD HH ...
  • C(Chapter) C-01.資料庫概述 1.為什麼要用資料庫 持久化(persistence):把數據保存到可掉電式存儲設備(硬碟)中以供之後使用。大多數情況下,特別是企業應用,數據持久化是將記憶體中的數據保存到硬碟上加以"固化",而持久化的實現過程大多使用各種關係資料庫來完成。 持久化的主要作用 ...
  • 1、問題描述 1.1、人大金倉資料庫訪問失敗 1.2、人大金倉資料庫埠連接失敗 1.3、人大金倉資料庫服務啟動失敗 2、問題分析 出現上述情況,很大可能是由於授權碼文件過期了,更新授權碼文件即可恢復正常。 3、解決方式 3.1、下載授權碼文件 官網地址:https://www.kingbase.c ...
  • 前言 2023 年某一天周末,新手程式員小明因為領導安排的一個活來到公司加班,小明三下五除二,按照領導要求寫了一個跑批的數據落庫任務在測試環境執行 ,突然間公司停電了,小明大驚,“糟了,MySQL 還在跑任務,會不會因為突然斷電,導致資料庫崩了”。 這時候,傍邊的同事雲淡風清的說了一句,“沒事,小明 ...
  • SQL托管 如果您希望您的網站能夠存儲和檢索數據,您的Web伺服器應該能夠訪問使用SQL語言的資料庫系統。以下是一些常見的SQL托管選項: MS SQL Server Microsoft的SQL Server是一個流行的資料庫軟體,適用於具有高流量的資料庫驅動網站。它是一個強大、穩健且功能齊全的SQ ...
  • 最近有個需求需要實現自定義首頁佈局,需要將屏幕按照 6 列 4 行進行等分成多個格子,然後將組件可拖拽對應格子進行渲染展示。 示例 對比一些已有的插件,發現想要實現產品的交互效果,沒有現成可用的。本身功能並不是太過複雜,於是決定自己基於 vue 手擼一個簡易的 Grid 拖拽佈局。 完整源碼在此,在 ...
  • 參考視頻: 黑馬程式員2023新版JavaWeb開發教程,實現javaweb企業開發全流程 【小飛非系列】最新Maven實戰教程-項目實戰構建利器 一.下載Maven安裝包 註意安裝maven前要先安裝jdk環境: JDK11版本安裝包下載地址 1.下載安裝包,存放在沒有中文的路徑中 Maven安裝 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...