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

歡迎訪問 生活随笔!

生活随笔

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

编程问答

CoreCLR源码探索(五) GC内存收集器的内部实现 调试篇

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

在上一篇中我分析了CoreCLR中GC的內部處理,
在這一篇我將使用LLDB實際跟蹤CoreCLR中GC,關于如何使用LLDB調試CoreCLR的介紹可以看:

  • 微軟官方的文檔,地址

  • 我在第3篇中的介紹,地址

  • LLDB官方的入門文檔,地址

源代碼

本篇跟蹤程序的源代碼如下:

using System;using System.Runtime.InteropServices;namespace ConsoleApplication{ ? ?public class Program{ ? ? ? ?public class ClassA { } ? ? ? ?public class ClassB { } ? ? ? ?public class ClassC { } ? ? ? ?public static void Main(string[] args) ? ? ? ?{ ? ? ? ? ? ?var a = new ClassA();{ var b = new ClassB(); } ? ? ? ? ? ?var c = new ClassC();GCHandle handle = GCHandle.Alloc(c, GCHandleType.Pinned);IntPtr address = handle.AddrOfPinnedObject();Console.WriteLine((long)address);GC.Collect();Console.WriteLine("first collect completed");c = null;GC.Collect();Console.WriteLine("second collect completed");GC.Collect();Console.WriteLine("third collect completed");}} }

準備調試

環境和我的第三篇文章一樣,都是ubuntu 16.04 LTS,首先需要發布程序:

dotnet publish

發布程序后,把自己編譯的coreclr文件覆蓋到發布目錄中:
復制coreclr/bin/Product/Linux.x64.Debug下的文件到程序目錄/bin/Debug/netcoreapp1.1/ubuntu.16.04-x64/publish下。
請不要設置開啟服務器GC,一來是這篇文章分析的是工作站GC的處理,二來開啟服務器GC很容易導致調試時死鎖。

進入調試

準備工作完成以后就可以進入調試了

cd 程序目錄/bin/Debug/netcoreapp1.1/ubuntu.16.04-x64/publish lldb-3.6 程序名稱

首先設置gc主函數的斷點,然后運行程序

b gc1 r

我們停在了gc1函數,現在可以用bt來看調用來源

這次是手動觸發GC,調用來源中包含了GCInterface::Collect和JIT生成的函數

需要顯示當前的本地變量可以用fr v,需要打印變量或者表達式可以用p

現在用n來步過,用s來步進繼續跟蹤代碼

進入標記階段

在上圖的位置中用s命令即可進入mark_phase,繼續步過到下圖的位置

這時先讓我們看下堆中的對象,加載CoreCLR提供的LLDB插件

plugin load libsosplugin.so

插件提供的命令可以查看這里的文檔

執行dumpheap查看堆中的狀態


執行dso查看堆和寄存器中引用的對象

執行dumpobj查看對象的信息

在這一輪gc中對象a b c都會存活下來,
可能你會對為什么b能存活下來感到驚訝,對象b的引用分配在棧上,即時生命周期過了也不一定會失效(rsp不會移回去)

br s -n Promote -c "(long)*ppObject == 0x00007fff5c01a2b8" # -n 名稱 -c 條件c # 繼續執行

接下來步進mark_object_simple函數,然后步進gc_mark1函數

me re -s8 -c3 -fx o # 顯示地址中的內存,8個字節一組,3組,hex格式,地址是op ((CObjectHeader*)o)->IsMarked() # 顯示對象是否標記存活

我們可以清楚的看到標記對象存活設置了MethodTable的指針|= 1

現在給PinObject下斷點

br s -n PinObject -c "(long)*pObjRef == 0x00007fff5c01a1a0"c

可以看到只是調用Promote然后傳入GC_CALL_PINNED

繼續步進到if (flags & GC_CALL_PINNED)下的pin_object

可以看到pinned標記設置在同步索引塊中

進入計劃階段

進入計劃階段后首先打印一下各個代的狀態

p generation_table

使用這個命令可以看到gen 0 ~ gen 3的狀態,最后一個元素是空元素不用在意

繼續步過下去到下圖的這一段

在這里我們找到了一個plug的開始,然后枚舉已標記的對象,下圖是擦除marked和pinned標記的代碼

在這里我們找到了一個plug的結束

如果是Full GC或者不升代,在處理第一個plug之前就會設置gen 2的計劃代邊界

模擬壓縮的地址

如果x越過原來的gen 0的邊界,設置gen 1的計劃代邊界(原gen 1的對象變gen 2),
如果不升代這里也會設置gen 0的計劃代邊界

模擬壓縮后把原地址與壓縮到的地址的偏移值存到plug信息(plug前的一塊內存)中

構建plug樹

設置brick表,這個plug樹跨了6個brick


如果升代,模擬壓縮全部完成后設置gen 0的計劃代邊界

接下來如果不動里面的變量,將會進入清掃階段(不滿足進入壓縮階段的條件)

進入清掃階段

這次為了觀察對象c如何被清掃,我們進入第二次gc的make_free_lists

b make_free_listsc

處理當前brick中的plug樹

前面看到的對象c的地址是0x00007fff5c01a2e8,這里我們就看對象c后面的plug是如何處理的

br s -f gc.cpp -l 23070 -c "(long)tree > 0x00007fff5c01a2e8"c

我們可以看到plug 0x00007fff5c01a300前面的空余空間中包含了對象c,空余空間的開始地址就是對象c

接下來就是在這片空余空間中創建free object和加到free list了,
這里的大小不足(< min_free_list)所以只會創建free object不會加到free list中

設置代邊界,之前計劃階段模擬的計劃代邊界不會被使用

清掃階段完成后這次的gc的主要工作就完成了,接下來讓我們看重定位階段和壓縮階段

進入重定位階段

使用上面的程序讓計劃階段選擇壓縮,需要修改變量,這里重新運行程序并使用以下命令

b gc.cpp:22489cexpr should_compact = true

n步過到下圖的位置,s步進到relocate_phase函數

到這個位置可以看到用了和標記階段一樣的GcScanRoots函數,但是傳入的不是Promote而是Relocate函數

接下來下斷點進入Relocate函數

b Relocatec

GCHeap::Relocate函數不會重定位子對象,只是用來重定位來源于根對象的引用

一直走到這個位置然后進入gc_heap::relocate_address函數

根據原地址和brick table找到對應的plug樹

搜索plug樹中old_address所屬的plug

根據plug中的reloc修改指針地址

現在再來看relocate_survivors函數,這個函數用于重定位存活下來的對象中的引用

b relocate_survivorsc

接下來會枚舉并處理brick,走到這里進入relocate_survivors_in_brick函數,這個函數處理單個brick中的plug樹

遞歸處理plug樹種的各個節點

走到這里進入relocate_survivors_in_plug函數,這個函數處理單個plug中的對象

圖中的這個plug結尾被下一個plug覆蓋過,需要特殊處理,這里繼續進入relocate_shortened_survivor_helper函數

當前是unpinned plug,下一個plug是pinned plug

枚舉處理plug中的各個對象

如果這個對象結尾未被覆蓋,則調用relocate_obj_helper重定位對象中的各個成員


如果對象結尾被覆蓋了,則調用relocate_shortened_obj_helper重定位對象中的各個成員
在這里成員如果被覆蓋會調用reloc_ref_in_shortened_obj修改備份數據中的成員,但是因為go_through_object_nostart是一個macro這里無法調試內部的代碼

接下來我們觀察對象a的地址是否改變了

重新運行并修改should_compact變量

b gc.cpp:22489r expr should_compact = trueplugin load libsosplugin.so dso

我們可以看到對象a的地址在0x00007fff5c01a2b8,接下來給relocate_address函數下斷點

br s -n relocate_address -c "(long)(*pold_address) == 0x00007fff5c01a2b8"c

我們可以看到地址由0x00007fff5c01a2b8變成了0x00007fff5c0091b8

接下來一直跳回plan_phase,下圖可以看到重定位階段完成以后新的地址上仍無對象,重定位階段只是修改了地址并未復制內存,直到壓縮階段完成以后對象才會在新的地址

接下來看壓縮階段

進入壓縮階段

在重定位階段完成以后走到下圖的位置,步進即可進入壓縮階段

枚舉brick table

處理單個brick table中的plug樹

根據下一個tree的gap計算last_plug的大小

處理單個plug中的對象

上面的last_plug是pinned plug所以不移動,這里找了另外一個會移動的plug

下圖可以看到整個plug都被復制到新的地址

這里再找一個結尾被覆蓋過的plug看看是怎么處理的

首先把被覆蓋的結尾大小加回去

然后把被覆蓋的內容臨時恢復回去


復制完再把覆蓋的內容交換回來,因為下一個plug還需要用

最終在recover_saved_pinned_info會全部恢復回去

參考鏈接

https://github.com/dotnet/coreclr/blob/master/Documentation/botr/garbage-collection.md
https://github.com/dotnet/coreclr/blob/release/1.1.0/Documentation/building/linux-instructions.md
https://github.com/dotnet/coreclr/blob/release/1.1.0/Documentation/building/debugging-instructions.md
http://lldb.llvm.org/tutorial.html
http://lldb.llvm.org/lldb-gdb.html

寫在最后

這一篇中我列出了幾個gc中比較關鍵的部分,但是還有成千上百處可以探討的部分,
如果你有興趣可以自己試著用lldb調試CoreCLR,可以學到很多文檔和書籍之外的知識,
特別是對于CoreCLR這種文檔少注釋也少的項目,掌握調試工具可以大幅減少理解代碼所需的時間

寫完這一篇我將暫停研究GC,下一篇開始會介紹JIT相關的內容,敬請期待

相關文章:

  • 《代碼的未來》讀書筆記:內存管理與GC那點事兒

  • CoreCLR源碼探索(一) Object是什么

  • CoreCLR源碼探索(二) new是什么

  • CoreCLR源碼探索(三) GC內存分配器的內部實現

  • .NET跨平臺之旅:corehost 是如何加載 coreclr 的

  • .NET CoreCLR開發人員指南(上)

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


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

總結

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

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