CoreCLR源码探索(三) GC内存分配器的内部实现
在前一篇中我講解了new是怎么工作的, 但是卻一筆跳過了內(nèi)存分配相關(guān)的部分.
在這一篇中我將詳細(xì)講解GC內(nèi)存分配器的內(nèi)部實(shí)現(xiàn).
在看這一篇之前請必須先看完微軟BOTR文檔中的"Garbage Collection Design",
原文地址是: https://github.com/dotnet/coreclr/blob/master/Documentation/botr/garbage-collection.md
譯文可以看知平軟件的譯文或我后來的譯文
請務(wù)必先看完"Garbage Collection Design", 否則以下內(nèi)容你很可能會無法理解
服務(wù)器GC和工作站GC
關(guān)于服務(wù)器GC和工作站GC的區(qū)別, 網(wǎng)上已經(jīng)有很多資料講解這篇就不再說明了.
我們來看服務(wù)器GC和工作站GC的代碼是怎么區(qū)別開來的.
默認(rèn)編譯CoreCLR會對同一份代碼以使用服務(wù)器GC還是工作站GC的區(qū)別編譯兩次, 分別在SVR和WKS命名空間中:
源代碼: https://github.com/dotnet/coreclr/blob/release/1.1.0/src/gc/gcsvr.cpp
namespace SVR { }源代碼: https://github.com/dotnet/coreclr/blob/release/1.1.0/src/gc/gcwks.cpp
namespace WKS { }當(dāng)定義了SERVER_GC時(shí), MULTIPLE_HEAPS和會被同時(shí)定義.
定義了MULTIPLE_HEAPS會使用多個(gè)堆(Heap), 服務(wù)器GC每個(gè)cpu核心都會對應(yīng)一個(gè)堆(默認(rèn)), 工作站GC則全局使用同一個(gè)堆.
源代碼: https://github.com/dotnet/coreclr/blob/release/1.1.0/src/gc/gcimpl.h
后臺GC無論是服務(wù)器GC還是工作站GC都會默認(rèn)支持, 但運(yùn)行時(shí)不一定會啟用.
源代碼: https://github.com/dotnet/coreclr/blob/release/1.1.0/src/gc/gcpriv.h
我們從https://www.microsoft.com/net下回來的CoreCLR安裝包中已經(jīng)包含了服務(wù)器GC和后臺GC的支持,但默認(rèn)不會開啟.
開啟它們可以修改project.json中的·runtimeOptions·節(jié), 例子如下:
設(shè)置后發(fā)布項(xiàng)目可以看到coreapp.runtimeconfig.json, 運(yùn)行時(shí)會只看這個(gè)文件.
微軟官方的文檔: https://docs.microsoft.com/en-us/dotnet/articles/core/tools/project-json
GC相關(guān)的類和它們的關(guān)系
我先用兩張圖來解釋服務(wù)器GC和工作站GC下GC相關(guān)的類的關(guān)系
圖中一共有5個(gè)類型
GCHeap
實(shí)現(xiàn)了IGCHeap接口, 公開GC層的接口給EE(運(yùn)行引擎)層調(diào)用
在工作站GC下只有一個(gè)實(shí)例, 不會關(guān)聯(lián)gc_heap對象, 因?yàn)楣ぷ髡綠C下gc_heap的所有成員都會被定義為靜態(tài)變量
在服務(wù)器GC下有1+cpu核心數(shù)個(gè)實(shí)例(默認(rèn)), 第一個(gè)實(shí)例用于當(dāng)接口, 其它對應(yīng)cpu核心的實(shí)例都會各關(guān)聯(lián)一個(gè)gc_heap實(shí)例
gc_heap
內(nèi)部的使用的堆類型, 用于負(fù)責(zé)內(nèi)存的分配和回收
在工作站GC下無實(shí)例, 所有成員都會定義為靜態(tài)變量
在工作站GC下generation_table這個(gè)成員不會被定義, 而是使用全局變量generation_table
在服務(wù)器GC下有cpu核心數(shù)個(gè)實(shí)例(默認(rèn)), 各關(guān)聯(lián)一個(gè)GCHeap實(shí)例
generation
儲存各個(gè)代的信息, 例如地址范圍和使用的段
儲存在generation_table中, 一個(gè)generation_table包含了5個(gè)generation, 前面的是0 1 2 3代, 最后一個(gè)不會被初始化和使用
在工作站GC下只有1個(gè)generation_table, 就是全局變量generation_table
在服務(wù)器GC下generation_table是gc_heap的成員, 有多少個(gè)gc_heap就有多少個(gè)generation_table
heap_segment
堆段, 供分配器使用的一段內(nèi)存, 用鏈表形式保存
每個(gè)gc_heap中都有一個(gè)或一個(gè)以上的segment
每個(gè)gc_heap中都有一個(gè)ephemeral heap segment(用于存放最年輕對象)
每個(gè)gc_heap中都有一個(gè)large heap segment(用于存放大對象)
在工作站GC下segment的默認(rèn)大小是256M(0x10000000字節(jié))
在服務(wù)器GC下segment的默認(rèn)大小是4G(0x100000000字節(jié))
alloc_context
分配上下文, 指向segment中的一個(gè)范圍, 用于實(shí)際分配對象
每個(gè)線程都有自己的分配上下文, 因?yàn)橹赶虻姆秶灰粯铀灾灰?dāng)前范圍還有足夠空間, 分配對象時(shí)不需要線程鎖
分配上下文的默認(rèn)范圍是8K, 也叫分配單位(Allocation Quantum)
分配小對象時(shí)會從這8K中分配, 分配大對象時(shí)則會直接從段(segment)中分配
代0(gen 0)還有一個(gè)默認(rèn)的分配上下文供內(nèi)部使用, 和線程無關(guān)
GCHeap的源代碼摘要:
GCHeap的定義: https://github.com/dotnet/coreclr/blob/release/1.1.0/src/gc/gcimpl.h#L61
全局的GCHeap實(shí)例: 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), 兩處地方都指向同一個(gè)實(shí)例.
// 相當(dāng)于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
這個(gè)類有300多個(gè)成員(從ephemeral_low開始),
generation的源代碼摘要:
generation的定義: https://github.com/dotnet/coreclr/blob/release/1.1.0/src/gc/gcpriv.h#L754
這里我只列出這篇文章涉及到的成員
heap_segment的源代碼摘要:
heap_segment的定義: https://github.com/dotnet/coreclr/blob/release/1.1.0/src/gc/gcpriv.h#L4166
這里我只列出這篇文章涉及到的成員
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, 但是成員還是一樣的
堆段的物理結(jié)構(gòu)
為了更好理解下面即將講解的代碼,請先看這兩張圖片
分配對象內(nèi)存的代碼流程
還記得上篇我提到過的AllocateObject函數(shù)嗎? 這個(gè)函數(shù)由JIT_New調(diào)用, 負(fù)責(zé)分配一個(gè)普通的對象.
讓我們來繼續(xù)跟蹤這個(gè)函數(shù)的內(nèi)部吧:
AllocateObject函數(shù)的內(nèi)容: https://github.com/dotnet/coreclr/blob/release/1.1.0/src/vm/gchelpers.cpp#L931
AllocateObject的其他版本同樣也會調(diào)用AllocAlign8或Alloc函數(shù), 下面就不再貼出其他版本的函數(shù)代碼了.
Alloc函數(shù)的內(nèi)容: https://github.com/dotnet/coreclr/blob/release/1.1.0/src/vm/gchelpers.cpp#L931
GetGCHeap函數(shù)的內(nèi)容: https://github.com/dotnet/coreclr/blob/release/1.1.0/src/gc/gc.h#L377
static GCHeap *GetGCHeap(){LIMITED_METHOD_CONTRACT; ? ?// 返回全局的GCHeap實(shí)例// 注意這個(gè)實(shí)例只作為接口使用,不和具體的gc_heap實(shí)例關(guān)聯(lián)_ASSERTE(g_pGCHeap != NULL); ? ?return g_pGCHeap; }GetThreadAllocContext函數(shù)的內(nèi)容: 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()); ??// 獲取當(dāng)前線程并返回m_alloc_context成員的地址return & GetThread()->m_alloc_context; }
GCHeap::Alloc函數(shù)的內(nèi)容: https://raw.githubusercontent.com/dotnet/coreclr/release/1.1.0/src/gc/gc.cpp
分配小對象內(nèi)存的代碼流程
讓我們來看一下小對象的內(nèi)存是如何分配的
allocate函數(shù)的內(nèi)容: https://raw.githubusercontent.com/dotnet/coreclr/release/1.1.0/src/gc/gc.cpp
這個(gè)函數(shù)嘗試從分配上下文分配內(nèi)存, 失敗時(shí)調(diào)用allocate_more_space為分配上下文指定新的空間
這里的前半部分的處理還有匯編版本, 可以看上一篇分析的JIT_TrialAllocSFastMP_InlineGetThread函數(shù)
allocate_more_space函數(shù)的內(nèi)容: https://raw.githubusercontent.com/dotnet/coreclr/release/1.1.0/src/gc/gc.cpp
這個(gè)函數(shù)會在有多個(gè)heap時(shí)調(diào)用balance_heaps平衡各個(gè)heap的使用量, 然后再調(diào)用try_allocate_more_space函數(shù)
try_allocate_more_space函數(shù)的內(nèi)容: https://raw.githubusercontent.com/dotnet/coreclr/release/1.1.0/src/gc/gc.cpp
這個(gè)函數(shù)會獲取MSL鎖, 檢查是否有必要觸發(fā)GC, 然后根據(jù)gen_number參數(shù)調(diào)用allocate_small或allocate_large函數(shù)
allocate_small函數(shù)的內(nèi)容: https://raw.githubusercontent.com/dotnet/coreclr/release/1.1.0/src/gc/gc.cpp
循環(huán)嘗試進(jìn)行各種回收內(nèi)存的處理和調(diào)用soh_try_fit函數(shù), soh_try_fit函數(shù)分配成功或手段已經(jīng)用盡時(shí)跳出循環(huán)
soh_try_fit函數(shù)的內(nèi)容: https://raw.githubusercontent.com/dotnet/coreclr/release/1.1.0/src/gc/gc.cpp
這個(gè)函數(shù)會先嘗試調(diào)用a_fit_free_list_p從自由對象列表中分配, 然后嘗試調(diào)用a_fit_segment_end_p從堆段結(jié)尾分配
a_fit_free_list_p函數(shù)的內(nèi)容: https://raw.githubusercontent.com/dotnet/coreclr/release/1.1.0/src/gc/gc.cpp
這個(gè)函數(shù)會嘗試從自由對象列表中找到足夠大小的空間, 如果找到則把分配上下文指向這個(gè)空間
a_fit_segment_end_p函數(shù)的內(nèi)容: https://raw.githubusercontent.com/dotnet/coreclr/release/1.1.0/src/gc/gc.cpp
這個(gè)函數(shù)會嘗試在堆段的結(jié)尾找到一塊足夠大小的空間, 如果找到則把分配上下文指向這個(gè)空間
adjust_limit_clr函數(shù)的內(nèi)容: https://raw.githubusercontent.com/dotnet/coreclr/release/1.1.0/src/gc/gc.cpp
這個(gè)函數(shù)會給分配上下文設(shè)置新的范圍
不管是從自由列表還是堆段的結(jié)尾分配都會調(diào)用這個(gè)函數(shù), 從自由列表分配時(shí)seg參數(shù)會是nullptr
調(diào)用完這個(gè)函數(shù)以后分配上下文就有足夠的空間了, 回到gc_heap::allocate的retry就可以成功的分配到對象的內(nèi)存
總結(jié)小對象內(nèi)存的代碼流程
allocate: 嘗試從分配上下文分配內(nèi)存, 失敗時(shí)調(diào)用allocate_more_space為分配上下文指定新的空間
try_allocate_more_space: 檢查是否有必要觸發(fā)GC, 然后根據(jù)gen_number參數(shù)調(diào)用allocate_small或allocate_large函數(shù)
soh_try_fit: 先嘗試調(diào)用a_fit_free_list_p從自由對象列表中分配, 然后嘗試調(diào)用a_fit_segment_end_p從堆段結(jié)尾分配
adjust_limit_clr: 給分配上下文設(shè)置新的范圍
adjust_limit_clr: 給分配上下文設(shè)置新的范圍
a_fit_free_list_p: 嘗試從自由對象列表中找到足夠大小的空間, 如果找到則把分配上下文指向這個(gè)空間
a_fit_segment_end_p: 嘗試在堆段的結(jié)尾找到一塊足夠大小的空間, 如果找到則把分配上下文指向這個(gè)空間
allocate_small: 循環(huán)嘗試進(jìn)行各種回收內(nèi)存的處理和調(diào)用soh_try_fit函數(shù)
allocate_more_space: 調(diào)用try_allocate_more_space函數(shù)
分配大對象內(nèi)存的代碼流程
讓我們來看一下大對象的內(nèi)存是如何分配的
分配小對象我們從gc_heap::allocate開始跟蹤, 這里我們從gc_heap::allocate_large_object開始跟蹤
allocate_large_object函數(shù)的內(nèi)容: https://raw.githubusercontent.com/dotnet/coreclr/release/1.1.0/src/gc/gc.cpp
這個(gè)函數(shù)和allocate函數(shù)不同的是它不會嘗試從分配上下文中分配, 而是直接從堆段中分配
allocate_more_space這個(gè)函數(shù)我們在之前已經(jīng)看過了, 忘掉的可以向前翻
這個(gè)函數(shù)會調(diào)用try_allocate_more_space函數(shù)
try_allocate_more_space函數(shù)在分配大對象時(shí)會調(diào)用allocate_large函數(shù)
allocate_large函數(shù)的內(nèi)容: https://raw.githubusercontent.com/dotnet/coreclr/release/1.1.0/src/gc/gc.cpp
這個(gè)函數(shù)的結(jié)構(gòu)和alloc_small相似但是內(nèi)部處理的細(xì)節(jié)不一樣
loh_try_fit函數(shù)的內(nèi)容: https://raw.githubusercontent.com/dotnet/coreclr/release/1.1.0/src/gc/gc.cpp
處理和soh_try_fit差不多, 先嘗試調(diào)用a_fit_free_list_large_p從自由對象列表中分配, 然后嘗試調(diào)用loh_a_fit_segment_end_p從堆段結(jié)尾分配
a_fit_free_list_large_p函數(shù)的內(nèi)容: https://raw.githubusercontent.com/dotnet/coreclr/release/1.1.0/src/gc/gc.cpp
和a_fit_free_list_p的處理基本相同, 但是在支持LOH壓縮時(shí)會生成填充對象, 并且有可能會調(diào)用bgc_loh_alloc_clr函數(shù)
adjust_limit_clr這個(gè)函數(shù)我們在看小對象的代碼流程時(shí)已經(jīng)看過
這里看bgc_loh_alloc_clr函數(shù)的內(nèi)容: https://raw.githubusercontent.com/dotnet/coreclr/release/1.1.0/src/gc/gc.cpp
這個(gè)函數(shù)是在后臺GC運(yùn)行時(shí)分配大對象使用的, 需要照顧到運(yùn)行中的后臺GC
loh_a_fit_segment_end_p函數(shù)的內(nèi)容: https://raw.githubusercontent.com/dotnet/coreclr/release/1.1.0/src/gc/gc.cpp
這個(gè)函數(shù)會遍歷第3代的堆段鏈表逐個(gè)調(diào)用a_fit_segment_end_p函數(shù)嘗試分配
總結(jié)大對象內(nèi)存的代碼流程
allocate_large_object: 調(diào)用allocate_more_space為一個(gè)空的分配上下文指定新的空間, 空間大小會等于對象的大小
try_allocate_more_space: 檢查是否有必要觸發(fā)GC, 然后根據(jù)gen_number參數(shù)調(diào)用allocate_small或allocate_large函數(shù)
loh_try_fit: 先嘗試調(diào)用a_fit_free_list_large_p從自由對象列表中分配, 然后嘗試調(diào)用loh_a_fit_segment_end_p從堆段結(jié)尾分配
a_fit_segment_end_p: 嘗試在堆段的結(jié)尾找到一塊足夠大小的空間, 如果找到則把分配上下文指向這個(gè)空間
bgc_loh_alloc_clr: 給分配上下文設(shè)置新的范圍, 照顧到后臺GC
adjust_limit_clr: 給分配上下文設(shè)置新的范圍
bgc_loh_alloc_clr: 給分配上下文設(shè)置新的范圍, 照顧到后臺GC
adjust_limit_clr: 給分配上下文設(shè)置新的范圍
a_fit_free_list_large_p: 嘗試從自由對象列表中找到足夠大小的空間, 如果找到則把分配上下文指向這個(gè)空間
loh_a_fit_segment_end_p: 遍歷第3代的堆段鏈表逐個(gè)調(diào)用a_fit_segment_end_p函數(shù)嘗試分配
allocate_large: 循環(huán)嘗試進(jìn)行各種回收內(nèi)存的處理和調(diào)用soh_try_fit函數(shù)
allocate_more_space: 調(diào)用try_allocate_more_space函數(shù)
CoreCLR如何管理系統(tǒng)內(nèi)存 (windows, linux)
看到這里我們應(yīng)該知道分配上下文, 小對象, 大對象的內(nèi)存都是來源于堆段, 那堆段的內(nèi)存來源于哪里呢?
GC在程序啟動時(shí)會創(chuàng)建默認(rèn)的堆段, 調(diào)用流程是init_gc_heap => get_initial_segment => make_heap_segment
如果默認(rèn)的堆段不夠用會創(chuàng)建新的堆段
小對象的堆段會通過gc1 => plan_phase => soh_get_segment_to_expand => get_segment => make_heap_segment創(chuàng)建
大對象的堆段會通過allocate_large => loh_get_new_seg => get_large_segment => get_segment_for_loh => get_segment => make_heap_segment創(chuàng)建
默認(rèn)的堆段會通過next_initial_memory分配內(nèi)存, 這一塊內(nèi)存在程序啟動時(shí)從reserve_initial_memory函數(shù)申請
reserve_initial_memory函數(shù)和make_heap_segment函數(shù)都會調(diào)用virtual_alloc函數(shù)
因?yàn)檎{(diào)用流程很長我這里就不一個(gè)個(gè)函數(shù)貼代碼了, 有興趣的可以自己去跟蹤
virtual_alloc函數(shù)的調(diào)用流程是
如果是windows, VirtualAlloc就是同名的windows api
如果是linux或者macosx, 調(diào)用流程是VirtualAlloc => VIRTUALReserveMemory => ReserveVirtualMemory
ReserveVirtualMemory函數(shù)會調(diào)用mmap函數(shù)
ReserveVirtualMemory函數(shù)的內(nèi)容: https://github.com/dotnet/coreclr/blob/release/1.1.0/src/pal/src/map/virtual.cpp#L894
CoreCLR在從系統(tǒng)申請內(nèi)存時(shí)會使用VirtualAlloc或mmap模擬的VirtualAlloc
申請后會得到一塊尚未完全提交到物理內(nèi)存的虛擬內(nèi)存(注意保護(hù)模式是PROT_NONE, 表示該塊內(nèi)存不能讀寫執(zhí)行, 內(nèi)核無需設(shè)置它的PageTable)
如果你有興趣可以看一下CoreCLR的虛擬內(nèi)存占用, 工作站GC啟動時(shí)就占了1G多, 服務(wù)器GC啟動時(shí)就占用了20G
之后CoreCLR會根據(jù)使用慢慢的把使用的部分提交到物理內(nèi)存, 流程是
GCToOSInterface::VirtualCommit => ClrVirtualAlloc => CExecutionEngine::ClrVirtualAlloc => EEVirtualAlloc => VirtualAlloc如果是windows, VirtualAlloc是同名的windowsapi, 地址會被顯式指定且頁保護(hù)模式為可讀寫(PAGE_READWRITE)
如果是linux或者macosx, VirtualAlloc會調(diào)用VIRTUALCommitMemory, 且內(nèi)部會調(diào)用mprotect來設(shè)置該頁為可讀寫(PROT_READ|PROT_WRITE)
當(dāng)GC回收了垃圾對象, 不再需要部分內(nèi)存時(shí)會把內(nèi)存還給系統(tǒng), 例如回收小對象后的流程是
gc1 => decommit_ephemeral_segment_pages => decommit_heap_segment_pages => GCToOSInterface::VirtualDecommitGCToOSInterface::VirtualDecommit的調(diào)用流程是
GCToOSInterface::VirtualDecommit => ClrVirtualFree => CExecutionEngine::ClrVirtualFree => EEVirtualFree => VirtualFree如果是windows, VirtualFree是同名的windowsapi, 表示該部分虛擬內(nèi)存已經(jīng)不再使用內(nèi)核可以重置它們的PageTable
如果是linux或者macosx, VirtualFree通過mprotect模擬, 設(shè)置該頁的保護(hù)模式為PROT_NONE
VirtualFree函數(shù)的內(nèi)容: https://github.com/dotnet/coreclr/blob/release/1.1.0/src/pal/src/map/virtual.cpp#L1291
我們可以看出, CoreCLR管理系統(tǒng)內(nèi)存的方式比較底層
在windows上使用了VirtualAlloc和VirtualFree
在linux上使用了mmap和mprotect
而不是使用傳統(tǒng)的malloc和new
這樣會帶來更好的性能但同時(shí)增加了移植到其他平臺的成本
動態(tài)調(diào)試GC分配對象內(nèi)存的過程
要深入學(xué)習(xí)CoreCLR光看代碼是很難做到的, 比如這次大部分來源的gc.cpp有接近37000行的代碼, 如果直接看可以把一個(gè)像我這樣的普通人看瘋
為了很好的了解CoreCLR的工作原理這次我自己編譯了CoreCLR并在本地用lldb進(jìn)行了調(diào)試, 這里我分享一下編譯和調(diào)試的過程
這里我使用了ubuntu 16.04 LTS, 因?yàn)閘inux上部署編譯環(huán)境比windows要簡單很多
下載CORECLR:
git clone https://github.com/dotnet/coreclr.git切換到你正在使用的版本, 請務(wù)必切換不要直接去編譯master分支
git checkout v1.1.0參考微軟的幫助安裝好需要的包
echo "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執(zhí)行build.sh會從微軟的網(wǎng)站下載一些東西, 如果很長時(shí)間都下載不成功你應(yīng)該考慮掛點(diǎn)什么東西
編譯過程需要幾十分鐘, 完成以后可以在coreclr/bin/Product/Linux.x64.Debug下看到編譯結(jié)果
完成以后用dotnet創(chuàng)建一個(gè)新的可執(zhí)行項(xiàng)目, 在project.json中添加runtimes節(jié)
{"runtimes": {"ubuntu.16.04-x64": {}} }Program.cs的代碼可以隨意寫, 想測哪部分就寫哪部分的代碼,我這里寫的是多線程分配內(nèi)存然后釋放的代碼
寫完以后編譯并發(fā)布
dotnet restoredotnet publish發(fā)布后bin/Debug/netcoreapp1.1/ubuntu16.04-x64/publish會多出最終發(fā)布的文件
把剛才CoreCLR編譯出來的coreclr/bin/Product/Linux.x64.Debug下的所有文件復(fù)制到publish目錄下, 并覆蓋原有文件
微軟官方的調(diào)試文檔可見 https://github.com/dotnet/coreclr/blob/release/1.1.0/Documentation/building/debugging-instructions.md
使用lldb啟動進(jìn)程, 這里我項(xiàng)目名稱是coreapp所以publish下的可執(zhí)行文件名稱也是coreapp
lldb-3.6 ./coreapp啟動進(jìn)程后可以打命令來調(diào)試, 需要中斷(暫停)程序運(yùn)行可以按下ctrl+c
這張圖中的命令
gc_heap::allocate_small 但是lldb允許用短名稱下斷點(diǎn), 碰到多個(gè)符合的函數(shù)會一并截取r 運(yùn)行程序, 之前在pending中的斷點(diǎn)如果在程序運(yùn)行后可以確定內(nèi)存位置則實(shí)際的添加斷點(diǎn)bt 查看當(dāng)前的堆棧調(diào)用樹, 可以看當(dāng)前被調(diào)用的函數(shù)的來源是哪些函數(shù)
這張圖中的命令
這張圖中的命令
這張圖顯示的是線程列表中的第一個(gè)線程的分配上下文內(nèi)容, 0x168可以通過p &((Thread*)nullptr)->m_Link計(jì)算得出(就是offsetof)
這張圖中的命令
lldb不僅能調(diào)試CoreCLR自身的代碼
還能用來調(diào)試用戶寫的程序代碼, 需要微軟的SOS插件支持
詳細(xì)可以看微軟的官方文檔 https://github.com/dotnet/coreclr/blob/release/1.1.0/Documentation/building/debugging-instructions.md
最后附上在這次分析中我常用的lldb命令
學(xué)習(xí)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
因?yàn)間c的代碼實(shí)在龐大并且注釋少, 這次的分析我不僅在官方的github上提問了還動用到lldb才能做到初步的理解
下一篇我將講解GC內(nèi)存回收器的內(nèi)部實(shí)現(xiàn), 可能需要的時(shí)間更長, 請耐心等待吧
原文地址:http://www.cnblogs.com/zkweb/p/6379080.html
.NET社區(qū)新聞,深度好文,微信中搜索dotNET跨平臺或掃描二維碼關(guān)注
總結(jié)
以上是生活随笔為你收集整理的CoreCLR源码探索(三) GC内存分配器的内部实现的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 虚拟研讨会:.NET的未来在哪里?
- 下一篇: 20周年献礼:Visual Studio