日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

CoreCLR源码探索(三) GC内存分配器的内部实现

發布時間:2023/12/4 编程问答 34 豆豆
生活随笔 收集整理的這篇文章主要介紹了 CoreCLR源码探索(三) GC内存分配器的内部实现 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

在前一篇中我講解了new是怎么工作的, 但是卻一筆跳過了內存分配相關的部分.
在這一篇中我將詳細講解GC內存分配器的內部實現.
在看這一篇之前請必須先看完微軟BOTR文檔中的"Garbage Collection Design",
原文地址是: https://github.com/dotnet/coreclr/blob/master/Documentation/botr/garbage-collection.md
譯文可以看知平軟件的譯文或我后來的譯文
請務必先看完"Garbage Collection Design", 否則以下內容你很可能會無法理解

服務器GC和工作站GC

關于服務器GC和工作站GC的區別, 網上已經有很多資料講解這篇就不再說明了.
我們來看服務器GC和工作站GC的代碼是怎么區別開來的.
默認編譯CoreCLR會對同一份代碼以使用服務器GC還是工作站GC的區別編譯兩次, 分別在SVR和WKS命名空間中:

源代碼: https://github.com/dotnet/coreclr/blob/release/1.1.0/src/gc/gcsvr.cpp

#define SERVER_GC 1namespace SVR { #include "gcimpl.h"#include "gc.cpp"}

源代碼: https://github.com/dotnet/coreclr/blob/release/1.1.0/src/gc/gcwks.cpp

#ifdef SERVER_GC#undef SERVER_GC#endifnamespace WKS { #include "gcimpl.h"#include "gc.cpp"}

當定義了SERVER_GC時, MULTIPLE_HEAPS和會被同時定義.
定義了MULTIPLE_HEAPS會使用多個堆(Heap), 服務器GC每個cpu核心都會對應一個堆(默認), 工作站GC則全局使用同一個堆.

源代碼: https://github.com/dotnet/coreclr/blob/release/1.1.0/src/gc/gcimpl.h

#ifdef SERVER_GC#define MULTIPLE_HEAPS 1#endif // SERVER_GC

后臺GC無論是服務器GC還是工作站GC都會默認支持, 但運行時不一定會啟用.

源代碼: https://github.com/dotnet/coreclr/blob/release/1.1.0/src/gc/gcpriv.h

#define BACKGROUND_GC //concurrent background GC (requires WRITE_WATCH)

我們從https://www.microsoft.com/net下回來的CoreCLR安裝包中已經包含了服務器GC和后臺GC的支持,但默認不會開啟.
開啟它們可以修改project.json中的·runtimeOptions·節, 例子如下:

{"runtimeOptions": {"configProperties": {"System.GC.Server": true,"System.GC.Concurrent": true}}}

設置后發布項目可以看到coreapp.runtimeconfig.json, 運行時會只看這個文件.
微軟官方的文檔: https://docs.microsoft.com/en-us/dotnet/articles/core/tools/project-json

GC相關的類和它們的關系

我先用兩張圖來解釋服務器GC和工作站GC下GC相關的類的關系

圖中一共有5個類型

  • GCHeap

    • 實現了IGCHeap接口, 公開GC層的接口給EE(運行引擎)層調用

    • 在工作站GC下只有一個實例, 不會關聯gc_heap對象, 因為工作站GC下gc_heap的所有成員都會被定義為靜態變量

    • 在服務器GC下有1+cpu核心數個實例(默認), 第一個實例用于當接口, 其它對應cpu核心的實例都會各關聯一個gc_heap實例

  • gc_heap

    • 內部的使用的堆類型, 用于負責內存的分配和回收

    • 在工作站GC下無實例, 所有成員都會定義為靜態變量

    • 在工作站GC下generation_table這個成員不會被定義, 而是使用全局變量generation_table

    • 在服務器GC下有cpu核心數個實例(默認), 各關聯一個GCHeap實例

  • generation

    • 儲存各個代的信息, 例如地址范圍和使用的段

    • 儲存在generation_table中, 一個generation_table包含了5個generation, 前面的是0 1 2 3代, 最后一個不會被初始化和使用

    • 在工作站GC下只有1個generation_table, 就是全局變量generation_table

    • 在服務器GC下generation_table是gc_heap的成員, 有多少個gc_heap就有多少個generation_table

  • heap_segment

    • 堆段, 供分配器使用的一段內存, 用鏈表形式保存

    • 每個gc_heap中都有一個或一個以上的segment

    • 每個gc_heap中都有一個ephemeral heap segment(用于存放最年輕對象)

    • 每個gc_heap中都有一個large heap segment(用于存放大對象)

    • 在工作站GC下segment的默認大小是256M(0x10000000字節)

    • 在服務器GC下segment的默認大小是4G(0x100000000字節)

  • alloc_context

    • 分配上下文, 指向segment中的一個范圍, 用于實際分配對象

    • 每個線程都有自己的分配上下文, 因為指向的范圍不一樣所以只要當前范圍還有足夠空間, 分配對象時不需要線程鎖

    • 分配上下文的默認范圍是8K, 也叫分配單位(Allocation Quantum)

    • 分配小對象時會從這8K中分配, 分配大對象時則會直接從段(segment)中分配

    • 代0(gen 0)還有一個默認的分配上下文供內部使用, 和線程無關

GCHeap的源代碼摘要:

GCHeap的定義: https://github.com/dotnet/coreclr/blob/release/1.1.0/src/gc/gcimpl.h#L61
全局的GCHeap實例: https://github.com/dotnet/coreclr/blob/release/1.1.0/src/gc/gc.h#L105

這里是1.1.0的代碼, 1.2.0全局GCHeap會分別保存到gcheaputilities.h(g_pGCHeap)和gc.cpp(g_theGCHeap), 兩處地方都指向同一個實例.

// 相當于extern GCHeap* g_pGCHeap;GPTR_DECL(GCHeap, g_pGCHeap);

gc_heap的源代碼摘要:

gc_heap的定義: https://github.com/dotnet/coreclr/blob/release/1.1.0/src/gc/gcpriv.h#L1079
這個類有300多個成員(從ephemeral_low開始),

generation的源代碼摘要:

generation的定義: https://github.com/dotnet/coreclr/blob/release/1.1.0/src/gc/gcpriv.h#L754
這里我只列出這篇文章涉及到的成員

class generation {public: ? ?// 默認的分配上下文alloc_context ? allocation_context; ? ?// 用于分配的最新的堆段heap_segment* ? allocation_segment; ? ?// 開始的堆段PTR_heap_segment start_segment; ? ?// 用于區分對象在哪個代的指針, 在此之后的對象都屬于這個代, 或比這個代更年輕的代uint8_t* ? ? ? ?allocation_start; ? ?// 用于儲存和分配自由對象(Free Object, 又名Unused Array, 可以理解為碎片空間)的分配器allocator ? ? ? free_list_allocator; ? ?// 這個代是第幾代int gen_num; };

heap_segment的源代碼摘要:

heap_segment的定義: https://github.com/dotnet/coreclr/blob/release/1.1.0/src/gc/gcpriv.h#L4166
這里我只列出這篇文章涉及到的成員

class heap_segment {public: ? ?// 已實際分配地址 (mem + 已分配大小)// 更新有可能會延遲uint8_t* ? ? ? ?allocated; ? ?// 已提交到物理內存的地址 (this + SEGMENT_INITIAL_COMMIT)uint8_t* ? ? ? ?committed; ? ?// 預留到的分配地址 (this + size)uint8_t* ? ? ? ?reserved; ? ?// 已使用地址 (mem + 已分配大小 - 對象頭大小)uint8_t* ? ? ? ?used; ? ?// 初始分配地址 (服務器gc開啟時: this + OS_PAGE_SIZE, 否則: this + sizeof(*this) + alignment)uint8_t* ? ? ? ?mem; ? ?// 下一個堆段PTR_heap_segment next; ? ?// 屬于的gc_heap實例gc_heap* ? ? ? ?heap; };

alloc_context的源代碼摘要:

alloc_context的定義: https://github.com/dotnet/coreclr/blob/release/1.1.0/src/gc/gc.h#L162
這里是1.1.0的代碼, 1.2.0這些成員移動到了gcinterface.h的gc_alloc_context, 但是成員還是一樣的

struct alloc_context { ? ?// 下一次分配對象的開始地址uint8_t* ? ? ? alloc_ptr; ? ?// 可以分配到的最終地址uint8_t* ? ? ? alloc_limit; ? ?// 歷史分配的小對象大小合計int64_t ? ? ? ?alloc_bytes; //Number of bytes allocated on SOH by this context// 歷史分配的大對象大小合計int64_t ? ? ? ?alloc_bytes_loh; //Number of bytes allocated on LOH by this context#if defined(FEATURE_SVR_GC)// 空間不夠需要獲取更多空間時使用的GCHeap// 分alloc_heap和home_heap的作用是平衡各個heap的使用量,這樣并行回收時可以減少處理各個heap的時間差異SVR::GCHeap* ? alloc_heap; ? ?// 原來的GCHeapSVR::GCHeap* ? home_heap;#endif // defined(FEATURE_SVR_GC)// 歷史分配對象次數int ? ? ? ? ? ?alloc_count; };

堆段的物理結構

為了更好理解下面即將講解的代碼,請先看這兩張圖片

分配對象內存的代碼流程

還記得上篇我提到過的AllocateObject函數嗎? 這個函數由JIT_New調用, 負責分配一個普通的對象.
讓我們來繼續跟蹤這個函數的內部吧:

AllocateObject函數的內容: https://github.com/dotnet/coreclr/blob/release/1.1.0/src/vm/gchelpers.cpp#L931
AllocateObject的其他版本同樣也會調用AllocAlign8或Alloc函數, 下面就不再貼出其他版本的函數代碼了.

Alloc函數的內容: https://github.com/dotnet/coreclr/blob/release/1.1.0/src/vm/gchelpers.cpp#L931

GetGCHeap函數的內容: https://github.com/dotnet/coreclr/blob/release/1.1.0/src/gc/gc.h#L377

static GCHeap *GetGCHeap(){LIMITED_METHOD_CONTRACT; ? ?// 返回全局的GCHeap實例// 注意這個實例只作為接口使用,不和具體的gc_heap實例關聯_ASSERTE(g_pGCHeap != NULL); ? ?return g_pGCHeap; }

GetThreadAllocContext函數的內容: https://github.com/dotnet/coreclr/blob/release/1.1.0/src/vm/gchelpers.cpp#L54

inline alloc_context* GetThreadAllocContext(){WRAPPER_NO_CONTRACT;assert(GCHeap::UseAllocationContexts()); ?
?// 獲取當前線程并返回m_alloc_context成員的地址return & GetThread()->m_alloc_context; }

GCHeap::Alloc函數的內容: https://raw.githubusercontent.com/dotnet/coreclr/release/1.1.0/src/gc/gc.cpp

分配小對象內存的代碼流程

讓我們來看一下小對象的內存是如何分配的

allocate函數的內容: https://raw.githubusercontent.com/dotnet/coreclr/release/1.1.0/src/gc/gc.cpp
這個函數嘗試從分配上下文分配內存, 失敗時調用allocate_more_space為分配上下文指定新的空間
這里的前半部分的處理還有匯編版本, 可以看上一篇分析的JIT_TrialAllocSFastMP_InlineGetThread函數

allocate_more_space函數的內容: https://raw.githubusercontent.com/dotnet/coreclr/release/1.1.0/src/gc/gc.cpp
這個函數會在有多個heap時調用balance_heaps平衡各個heap的使用量, 然后再調用try_allocate_more_space函數


try_allocate_more_space函數的內容: https://raw.githubusercontent.com/dotnet/coreclr/release/1.1.0/src/gc/gc.cpp
這個函數會獲取MSL鎖, 檢查是否有必要觸發GC, 然后根據gen_number參數調用allocate_small或allocate_large函數

allocate_small函數的內容: https://raw.githubusercontent.com/dotnet/coreclr/release/1.1.0/src/gc/gc.cpp
循環嘗試進行各種回收內存的處理和調用soh_try_fit函數, soh_try_fit函數分配成功或手段已經用盡時跳出循環


soh_try_fit函數的內容: https://raw.githubusercontent.com/dotnet/coreclr/release/1.1.0/src/gc/gc.cpp
這個函數會先嘗試調用a_fit_free_list_p從自由對象列表中分配, 然后嘗試調用a_fit_segment_end_p從堆段結尾分配


a_fit_free_list_p函數的內容: https://raw.githubusercontent.com/dotnet/coreclr/release/1.1.0/src/gc/gc.cpp
這個函數會嘗試從自由對象列表中找到足夠大小的空間, 如果找到則把分配上下文指向這個空間

a_fit_segment_end_p函數的內容: https://raw.githubusercontent.com/dotnet/coreclr/release/1.1.0/src/gc/gc.cpp
這個函數會嘗試在堆段的結尾找到一塊足夠大小的空間, 如果找到則把分配上下文指向這個空間

adjust_limit_clr函數的內容: https://raw.githubusercontent.com/dotnet/coreclr/release/1.1.0/src/gc/gc.cpp
這個函數會給分配上下文設置新的范圍
不管是從自由列表還是堆段的結尾分配都會調用這個函數, 從自由列表分配時seg參數會是nullptr
調用完這個函數以后分配上下文就有足夠的空間了, 回到gc_heap::allocate的retry就可以成功的分配到對象的內存

總結小對象內存的代碼流程

  • allocate: 嘗試從分配上下文分配內存, 失敗時調用allocate_more_space為分配上下文指定新的空間

    • try_allocate_more_space: 檢查是否有必要觸發GC, 然后根據gen_number參數調用allocate_small或allocate_large函數

    • soh_try_fit: 先嘗試調用a_fit_free_list_p從自由對象列表中分配, 然后嘗試調用a_fit_segment_end_p從堆段結尾分配

    • adjust_limit_clr: 給分配上下文設置新的范圍

    • adjust_limit_clr: 給分配上下文設置新的范圍

    • a_fit_free_list_p: 嘗試從自由對象列表中找到足夠大小的空間, 如果找到則把分配上下文指向這個空間

    • a_fit_segment_end_p: 嘗試在堆段的結尾找到一塊足夠大小的空間, 如果找到則把分配上下文指向這個空間

    • allocate_small: 循環嘗試進行各種回收內存的處理和調用soh_try_fit函數

    • allocate_more_space: 調用try_allocate_more_space函數

分配大對象內存的代碼流程

讓我們來看一下大對象的內存是如何分配的
分配小對象我們從gc_heap::allocate開始跟蹤, 這里我們從gc_heap::allocate_large_object開始跟蹤

allocate_large_object函數的內容: https://raw.githubusercontent.com/dotnet/coreclr/release/1.1.0/src/gc/gc.cpp
這個函數和allocate函數不同的是它不會嘗試從分配上下文中分配, 而是直接從堆段中分配

allocate_more_space這個函數我們在之前已經看過了, 忘掉的可以向前翻
這個函數會調用try_allocate_more_space函數
try_allocate_more_space函數在分配大對象時會調用allocate_large函數

allocate_large函數的內容: https://raw.githubusercontent.com/dotnet/coreclr/release/1.1.0/src/gc/gc.cpp
這個函數的結構和alloc_small相似但是內部處理的細節不一樣

loh_try_fit函數的內容: https://raw.githubusercontent.com/dotnet/coreclr/release/1.1.0/src/gc/gc.cpp
處理和soh_try_fit差不多, 先嘗試調用a_fit_free_list_large_p從自由對象列表中分配, 然后嘗試調用loh_a_fit_segment_end_p從堆段結尾分配


a_fit_free_list_large_p函數的內容: https://raw.githubusercontent.com/dotnet/coreclr/release/1.1.0/src/gc/gc.cpp
和a_fit_free_list_p的處理基本相同, 但是在支持LOH壓縮時會生成填充對象, 并且有可能會調用bgc_loh_alloc_clr函數

adjust_limit_clr這個函數我們在看小對象的代碼流程時已經看過
這里看bgc_loh_alloc_clr函數的內容: https://raw.githubusercontent.com/dotnet/coreclr/release/1.1.0/src/gc/gc.cpp
這個函數是在后臺GC運行時分配大對象使用的, 需要照顧到運行中的后臺GC


loh_a_fit_segment_end_p函數的內容: https://raw.githubusercontent.com/dotnet/coreclr/release/1.1.0/src/gc/gc.cpp
這個函數會遍歷第3代的堆段鏈表逐個調用a_fit_segment_end_p函數嘗試分配

總結大對象內存的代碼流程

  • allocate_large_object: 調用allocate_more_space為一個空的分配上下文指定新的空間, 空間大小會等于對象的大小

    • try_allocate_more_space: 檢查是否有必要觸發GC, 然后根據gen_number參數調用allocate_small或allocate_large函數

    • loh_try_fit: 先嘗試調用a_fit_free_list_large_p從自由對象列表中分配, 然后嘗試調用loh_a_fit_segment_end_p從堆段結尾分配

    • a_fit_segment_end_p: 嘗試在堆段的結尾找到一塊足夠大小的空間, 如果找到則把分配上下文指向這個空間

    • bgc_loh_alloc_clr: 給分配上下文設置新的范圍, 照顧到后臺GC

    • adjust_limit_clr: 給分配上下文設置新的范圍

    • bgc_loh_alloc_clr: 給分配上下文設置新的范圍, 照顧到后臺GC

    • adjust_limit_clr: 給分配上下文設置新的范圍

    • a_fit_free_list_large_p: 嘗試從自由對象列表中找到足夠大小的空間, 如果找到則把分配上下文指向這個空間

    • loh_a_fit_segment_end_p: 遍歷第3代的堆段鏈表逐個調用a_fit_segment_end_p函數嘗試分配

    • allocate_large: 循環嘗試進行各種回收內存的處理和調用soh_try_fit函數

    • allocate_more_space: 調用try_allocate_more_space函數

CoreCLR如何管理系統內存 (windows, linux)

看到這里我們應該知道分配上下文, 小對象, 大對象的內存都是來源于堆段, 那堆段的內存來源于哪里呢?
GC在程序啟動時會創建默認的堆段, 調用流程是init_gc_heap => get_initial_segment => make_heap_segment
如果默認的堆段不夠用會創建新的堆段
小對象的堆段會通過gc1 => plan_phase => soh_get_segment_to_expand => get_segment => make_heap_segment創建
大對象的堆段會通過allocate_large => loh_get_new_seg => get_large_segment => get_segment_for_loh => get_segment => make_heap_segment創建

默認的堆段會通過next_initial_memory分配內存, 這一塊內存在程序啟動時從reserve_initial_memory函數申請
reserve_initial_memory函數和make_heap_segment函數都會調用virtual_alloc函數

因為調用流程很長我這里就不一個個函數貼代碼了, 有興趣的可以自己去跟蹤
virtual_alloc函數的調用流程是

virtual_alloc => GCToOSInterface::VirtualReserve => ClrVirtualAllocAligned => ClrVirtualAlloc => CExecutionEngine::ClrVirtualAlloc => EEVirtualAlloc => VirtualAlloc

如果是windows, VirtualAlloc就是同名的windows api
如果是linux或者macosx, 調用流程是VirtualAlloc => VIRTUALReserveMemory => ReserveVirtualMemory
ReserveVirtualMemory函數會調用mmap函數

ReserveVirtualMemory函數的內容: https://github.com/dotnet/coreclr/blob/release/1.1.0/src/pal/src/map/virtual.cpp#L894

CoreCLR在從系統申請內存時會使用VirtualAlloc或mmap模擬的VirtualAlloc
申請后會得到一塊尚未完全提交到物理內存的虛擬內存(注意保護模式是PROT_NONE, 表示該塊內存不能讀寫執行, 內核無需設置它的PageTable)
如果你有興趣可以看一下CoreCLR的虛擬內存占用, 工作站GC啟動時就占了1G多, 服務器GC啟動時就占用了20G

之后CoreCLR會根據使用慢慢的把使用的部分提交到物理內存, 流程是

GCToOSInterface::VirtualCommit => ClrVirtualAlloc => CExecutionEngine::ClrVirtualAlloc => EEVirtualAlloc => VirtualAlloc

如果是windows, VirtualAlloc是同名的windowsapi, 地址會被顯式指定且頁保護模式為可讀寫(PAGE_READWRITE)
如果是linux或者macosx, VirtualAlloc會調用VIRTUALCommitMemory, 且內部會調用mprotect來設置該頁為可讀寫(PROT_READ|PROT_WRITE)

當GC回收了垃圾對象, 不再需要部分內存時會把內存還給系統, 例如回收小對象后的流程是

gc1 => decommit_ephemeral_segment_pages => decommit_heap_segment_pages => GCToOSInterface::VirtualDecommit

GCToOSInterface::VirtualDecommit的調用流程是

GCToOSInterface::VirtualDecommit => ClrVirtualFree => CExecutionEngine::ClrVirtualFree => EEVirtualFree => VirtualFree

如果是windows, VirtualFree是同名的windowsapi, 表示該部分虛擬內存已經不再使用內核可以重置它們的PageTable
如果是linux或者macosx, VirtualFree通過mprotect模擬, 設置該頁的保護模式為PROT_NONE

VirtualFree函數的內容: https://github.com/dotnet/coreclr/blob/release/1.1.0/src/pal/src/map/virtual.cpp#L1291

我們可以看出, CoreCLR管理系統內存的方式比較底層
在windows上使用了VirtualAlloc和VirtualFree
在linux上使用了mmap和mprotect
而不是使用傳統的malloc和new
這樣會帶來更好的性能但同時增加了移植到其他平臺的成本

動態調試GC分配對象內存的過程

要深入學習CoreCLR光看代碼是很難做到的, 比如這次大部分來源的gc.cpp有接近37000行的代碼, 如果直接看可以把一個像我這樣的普通人看瘋
為了很好的了解CoreCLR的工作原理這次我自己編譯了CoreCLR并在本地用lldb進行了調試, 這里我分享一下編譯和調試的過程
這里我使用了ubuntu 16.04 LTS, 因為linux上部署編譯環境比windows要簡單很多

下載CORECLR:

git clone https://github.com/dotnet/coreclr.git

切換到你正在使用的版本, 請務必切換不要直接去編譯master分支

git checkout v1.1.0

參考微軟的幫助安裝好需要的包

# https://github.com/dotnet/coreclr/blob/master/Documentation/building/linux-instructions.mdecho "deb http://llvm.org/apt/trusty/ llvm-toolchain-trusty-3.6 main" | sudo tee /etc/apt/sources.list.d/llvm.listwget -O - http://llvm.org/apt/llvm-snapshot.gpg.key | sudo apt-key add -sudo apt-get update sudo apt-get install cmake llvm-3.5 clang-3.5 lldb-3.6 lldb-3.6-dev libunwind8 libunwind8-dev gettext libicu-dev liblttng-ust-dev libcurl4-openssl-dev libssl-dev uuid-dev cd coreclr ./build.sh

執行build.sh會從微軟的網站下載一些東西, 如果很長時間都下載不成功你應該考慮掛點什么東西
編譯過程需要幾十分鐘, 完成以后可以在coreclr/bin/Product/Linux.x64.Debug下看到編譯結果

完成以后用dotnet創建一個新的可執行項目, 在project.json中添加runtimes節

{"runtimes": {"ubuntu.16.04-x64": {}} }

Program.cs的代碼可以隨意寫, 想測哪部分就寫哪部分的代碼,我這里寫的是多線程分配內存然后釋放的代碼

寫完以后編譯并發布

dotnet restoredotnet publish

發布后bin/Debug/netcoreapp1.1/ubuntu16.04-x64/publish會多出最終發布的文件
把剛才CoreCLR編譯出來的coreclr/bin/Product/Linux.x64.Debug下的所有文件復制到publish目錄下, 并覆蓋原有文件
微軟官方的調試文檔可見 https://github.com/dotnet/coreclr/blob/release/1.1.0/Documentation/building/debugging-instructions.md

使用lldb啟動進程, 這里我項目名稱是coreapp所以publish下的可執行文件名稱也是coreapp

lldb-3.6 ./coreapp

啟動進程后可以打命令來調試, 需要中斷(暫停)程序運行可以按下ctrl+c

這張圖中的命令

b allocate_small 給函數下斷點, 這里的allocate_small雖然全名是SVR::gc_heap::allocate_small或WKS::
gc_heap::allocate_small 但是lldb允許用短名稱下斷點, 碰到多個符合的函數會一并截取r 運行程序, 之前在pending中的斷點如果在程序運行后可以確定內存位置則實際的添加斷點bt 查看當前的堆棧調用樹, 可以看當前被調用的函數的來源是哪些函數


這張圖中的命令

n 步過, 遇到函數不會進去, 如果需要步進可以用s 另外步過匯編和步進匯編是ni和sifr v 查看當前堆棧幀中的變量 也就是傳入的參數和本地變量p acontext->alloc_ptr p *acontext打印全局或本地變量的值, 這個命令是調試中必用的命令, 不僅支持查看變量還支持計算表達式


這張圖中的命令

c繼續中斷進程直到退出或下一個斷點br del 刪除之前設置的所有斷點


這張圖顯示的是線程列表中的第一個線程的分配上下文內容, 0x168可以通過p &((Thread*)nullptr)->m_Link計算得出(就是offsetof)
這張圖中的命令

me re -s4 -fx -c12 0x00007fff5c006f00讀取0x00007fff5c006f00開始的內存, 單位是4byte, 表現形式是hex, 顯示12個單位


lldb不僅能調試CoreCLR自身的代碼
還能用來調試用戶寫的程序代碼, 需要微軟的SOS插件支持
詳細可以看微軟的官方文檔 https://github.com/dotnet/coreclr/blob/release/1.1.0/Documentation/building/debugging-instructions.md

最后附上在這次分析中我常用的lldb命令
學習lldb可以查看官方的Tutorial和GDB and LLDB command examples

參考鏈接

https://github.com/dotnet/coreclr/blob/master/Documentation/botr/garbage-collection.md
https://github.com/dotnet/coreclr/blob/release/1.1.0/src/gc/gcsvr.cpp
https://github.com/dotnet/coreclr/blob/release/1.1.0/src/gc/gcwks.cpp
https://github.com/dotnet/coreclr/blob/release/1.1.0/src/gc/gcimpl.h
https://github.com/dotnet/coreclr/blob/release/1.1.0/src/gc/gcpriv.h
https://github.com/dotnet/coreclr/blob/release/1.1.0/src/gc/gc.h#L162
https://github.com/dotnet/coreclr/blob/release/1.1.0/src/vm/gchelpers.cpp#L931
https://raw.githubusercontent.com/dotnet/coreclr/release/1.1.0/src/gc/gc.cpp
https://github.com/dotnet/coreclr/blob/release/1.1.0/src/pal/src/map/virtual.cpp#L894
https://github.com/dotnet/coreclr/blob/master/Documentation/building/linux-instructions.md
https://github.com/dotnet/coreclr/blob/release/1.1.0/Documentation/building/debugging-instructions.md
https://docs.microsoft.com/en-us/dotnet/articles/core/tools/project-json
https://github.com/dotnet/coreclr/issues/8959
https://github.com/dotnet/coreclr/issues/8995
https://github.com/dotnet/coreclr/issues/9053

因為gc的代碼實在龐大并且注釋少, 這次的分析我不僅在官方的github上提問了還動用到lldb才能做到初步的理解
下一篇我將講解GC內存回收器的內部實現, 可能需要的時間更長, 請耐心等待吧

原文地址:http://www.cnblogs.com/zkweb/p/6379080.html


.NET社區新聞,深度好文,微信中搜索dotNET跨平臺或掃描二維碼關注

總結

以上是生活随笔為你收集整理的CoreCLR源码探索(三) GC内存分配器的内部实现的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。