Advanced .Net Debugging 7:託管堆與垃圾收集

可均可可發表於2024-04-23


一、簡介

    這是我的《Advanced .Net Debugging》這個系列的第七篇文章。這篇文章的內容是原書的第二部分的【除錯實戰】的第五章,這一章主要講的是從根本上認識託管堆和垃圾回收。軟體系統的記憶體管理方式有兩種,第一種是手動管理記憶體,這種方式容易產生一些問題產生,比如:懸空指標、重複釋放,或者記憶體洩漏等;第二種是自動記憶體管理,比如:java 平臺、.NET 平臺。儘管 GC 能幫助開發人員簡化開發工作,讓他們更關注系統的業務功能實現。如果我們對 GC 運作原理了解更深入一些,也可以讓我們避免在垃圾回收環境中出現的問題。高階除錯會涉及很多方面的內容,你對 .NET 基礎知識掌握越全面、細節越底層,除錯成功的機率越大,當我們遇到各種奇葩問題的時候才不會手足無措。
    如果在沒有說明的情況下,所有程式碼的測試環境都是 Net 8.0,如果有變動,我會在專案章節裡進行說明。好了,廢話不多說,開始我們今天的除錯工作。

     除錯環境我需要進行說明,以防大家不清楚,具體情況我已經羅列出來。
          作業系統:Windows Professional 10
          除錯工具:Windbg Preview(Debugger Client:1.2306.1401.0,Debugger engine:10.0.25877.1004)和 NTSD(10.0.22621.2428 AMD64)
          下載地址:可以去Microsoft Store 去下載
          開發工具:Microsoft Visual Studio Community 2022 (64 位) - Current版本 17.8.3
          Net 版本:.Net 8.0
          CoreCLR原始碼:原始碼下載

    在此說明:我使用了兩種除錯工具,第一種:Windbg Preivew,圖形介面,使用方便,操作順手,不用擔心干擾;第二種是:NTSD,該偵錯程式是命令列的,命令使用上和 Windbg 沒有任何區別,之所以增加了它的除錯過程,不過是我的個人愛好,想多瞭解一些,看看他們有什麼區別,為了學習而使用的。如果在工作中,我推薦使用 Windbg Preview,更好用,更方便,也不會出現奇怪問題(我在使用 NTSD 除錯斷點的時候,不能斷住,提示記憶體不可讀,Windbg preview 就沒有任何問題)。
    如果大家想了解除錯過程,二選一即可,當然,我推薦檢視【Windbg Preview 除錯】。

二、除錯原始碼
    廢話不多說,本節是除錯的原始碼部分,沒有程式碼,當然就談不上測試了,除錯必須有載體。
    2.1、ExampleCore_5_1

Advanced .Net Debugging 7:託管堆與垃圾收集
 1 namespace ExampleCore_5_1
 2 {
 3     internal class Program
 4     {
 5         static void Main(string[] args)
 6         {
 7             Name? name;
 8 
 9             Console.WriteLine("Press any key to allocate memory");
10             Console.ReadKey();
11 
12             name = new Name("Mario", "Hewardt");
13 
14             Console.WriteLine("Press any key to Exit");
15             Console.ReadKey();
16         }
17     }
18 
19     internal class Name
20     {
21         private string _first;
22         private string _last;
23 
24         public string First { get { return _first; } }
25 
26         public string Last { get { return _last; } }
27 
28         public Name(string first, string last)
29         {
30             _first = first;
31             _last = last;
32         }
33     }
34 }
View Code

    2.2、ExampleCore_5_2

Advanced .Net Debugging 7:託管堆與垃圾收集
 1 namespace ExampleCore_5_2
 2 {
 3     internal class Name
 4     {
 5         private string _first;
 6         private string _last;
 7 
 8         public string First { get { return _first; } }
 9 
10         public string Last { get { return _last; } }
11 
12         public Name(string first, string last)
13         {
14             _first = first;
15             _last = last;
16         }
17     }
18 
19     internal class Program
20     {
21         static void Main(string[] args)
22         {
23             Name? n1 = new Name("Mario", "Hewardt");
24             Name? n2 = new Name("Gemma", "Hewardt");
25 
26             Console.WriteLine("allocated objects");
27 
28             Console.WriteLine("Press any key to invoke GC");
29             Console.ReadKey();
30 
31             n1 = null;
32             GC.Collect();
33 
34             Console.WriteLine("Press any key to invoke GC");
35             Console.ReadKey();
36 
37             GC.Collect();
38 
39             Console.WriteLine("Press any key to Exit");
40             Console.ReadKey();
41         }
42     }
43 }
View Code

    2.3、ExampleCore_5_3

Advanced .Net Debugging 7:託管堆與垃圾收集
 1 namespace ExampleCore_5_3
 2 {
 3     internal class Name
 4     {
 5         private string _first;
 6         private string _last;
 7 
 8         public string First { get { return _first; } }
 9 
10         public string Last { get { return _last; } }
11 
12         public Name(string first, string last)
13         {
14             _first = first;
15             _last = last;
16         }
17     }
18 
19     internal class Program
20     {
21         public static Name CompleteName = new Name("First", "Last");
22 
23         private Thread? thread;
24         private bool shouldExit;
25         static void Main(string[] args)
26         {
27             Program p = new Program();
28             p.Run();
29         }
30 
31         public void Run()
32         {
33             shouldExit = false;
34             Name n1 = CompleteName;
35 
36             thread = new Thread(Worker);
37             thread.Start(n1);
38 
39             Thread.Sleep(1000);
40 
41             Console.WriteLine("Press any key to Exit");
42             Console.ReadKey();
43 
44             shouldExit = true;
45         }
46 
47         public void Worker(object? o)
48         {
49             var n1 = (Name)o!;
50             Console.WriteLine("Thread started {0},{1}", n1.First, n1.Last);
51 
52             while (true)
53             {
54                 Thread.Sleep(500);
55                 if (shouldExit)
56                 {
57                     break;
58                 }
59             }
60         }
61     }
62 }
View Code

    2.4、ExampleCore_5_4

Advanced .Net Debugging 7:託管堆與垃圾收集
 1 using System.Runtime.InteropServices;
 2 
 3 namespace ExampleCore_5_4
 4 {
 5     internal class NativeEvent
 6     {
 7         private IntPtr _nativeHandle;
 8         public IntPtr NativeHandle { get { return _nativeHandle; } }
 9 
10         public NativeEvent(string name)
11         {
12             _nativeHandle = CreateEvent(IntPtr.Zero, false, true, name);
13         }
14 
15         ~NativeEvent()
16         {
17             if (_nativeHandle != IntPtr.Zero)
18             {
19                 CloseHandle(_nativeHandle);
20                 _nativeHandle = IntPtr.Zero;
21             }
22         }
23 
24         [DllImport("kernel32.dll")]
25         public static extern IntPtr CreateEvent(IntPtr lpEventAttributes, bool manualReset, bool initialState, string lpName);
26 
27         [DllImport("kernel32.dll")]
28         public static extern IntPtr CloseHandle(IntPtr lpEvent);
29     }
30 
31     internal class Program
32     {
33         static void Main(string[] args)
34         {
35             Program p = new Program();
36             p.Run();
37         }
38 
39         public void Run()
40         {
41             NativeEvent nativeEvent= new NativeEvent("MyNewEvent");
42 
43             Console.WriteLine("Press any key to GC1!");
44             Console.ReadKey();
45 
46             GC.Collect();
47 
48             Console.WriteLine("Press any key to GC2!");
49             Console.ReadKey();
50 
51             GC.Collect();
52 
53             Console.WriteLine("Press any key to Exit!");
54             Console.ReadKey();
55         }
56     }
57 }
View Code

    2.5、ExampleCore_5_5

Advanced .Net Debugging 7:託管堆與垃圾收集
 1 using System.Diagnostics;
 2 
 3 namespace ExampleCore_5_5
 4 {
 5     internal class Program
 6     {
 7         static void Main(string[] args)
 8         {
 9             Test();
10 
11             Console.WriteLine("1、物件已經分配,請檢視託管堆!");
12             Debugger.Break();
13             GC.Collect();
14 
15             Console.WriteLine("2、GC 已經觸發,請檢視託管堆中的 byte2");
16             Debugger.Break();
17 
18             Console.WriteLine("3、已分配 byte4,檢視是否 Free 塊中");
19             var byte4 = new byte[280000];
20             Debugger.Break();
21         }
22 
23         public static byte[]? byte1;
24         public static byte[]? byte3;
25 
26         private static void Test()
27         {
28             byte1 = new byte[185000];
29             var byte2 = new byte[285000];
30             byte3 = new byte[385000];
31         }
32     }
33 }
View Code

    2.7、ExampleCore_5_6

Advanced .Net Debugging 7:託管堆與垃圾收集
 1 using System.Runtime.InteropServices;
 2 
 3 namespace ExampleCore_5_6
 4 {
 5     internal class Program
 6     {
 7         static void Main(string[] args)
 8         {
 9             Program program = new Program();
10             program.Run();
11 
12             Console.WriteLine("Press any key to Exit!");
13             Console.ReadKey();
14         }
15 
16         public void Run()
17         {
18             sbyte[] b1;
19             sbyte[] b2;
20             sbyte[] b3;
21 
22             Console.WriteLine("Press any key to Alloc And Pinned!");
23             Console.ReadKey();
24 
25             b1 = new sbyte[100];
26             b2 = new sbyte[200];
27             b3 = new sbyte[300];
28 
29             GCHandle h1 = GCHandle.Alloc(b1, GCHandleType.Pinned);
30             GCHandle h2 = GCHandle.Alloc(b2, GCHandleType.Pinned);
31             GCHandle h3 = GCHandle.Alloc(b3, GCHandleType.Pinned);
32 
33             Console.WriteLine("Press any key to GC1!");
34             Console.ReadKey();
35 
36             GC.Collect();
37 
38             Console.WriteLine("Press any key to Free!");
39             Console.ReadKey();
40 
41             h1.Free();
42             h2.Free();
43             h3.Free();
44 
45             Console.WriteLine("Press any key to GC2!");
46             Console.ReadKey();
47 
48             GC.Collect();
49         }
50     }
51 }
View Code


    2.8、ExampleCore_5_7

Advanced .Net Debugging 7:託管堆與垃圾收集
 1 using System.Runtime.InteropServices;
 2 
 3 namespace ExampleCore_5_7
 4 {
 5     internal class Program
 6     {
 7         [DllImport("ExampleCore_5_8.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode)]
 8         public extern static int InitChars(char[] chars);
 9 
10         static void Main(string[] args)
11         {
12             char[] c = { 'a', 'b', 'c' };
13             var len = InitChars(c);
14 
15             GC.Collect();
16 
17             Console.WriteLine($"len={len}");
18             for (int i = 0; i < c.Count(); i++)
19             {
20                 Console.WriteLine($"{i}={c[i]}");
21             }
22             Console.Read();
23         }
24     }
25 }
View Code


    2.9、ExampleCore_5_8(C++)

Advanced .Net Debugging 7:託管堆與垃圾收集
 1 // ExampleCore_5_8.cpp : 此檔案包含 "main" 函式。程式執行將在此處開始並結束。
 2 //
 3 
 4 extern "C"
 5 {
 6     _declspec(dllexport) int InitChars(wchar_t c[]);
 7 }
 8 
 9 #include <iostream>
10 using namespace std;
11 
12 int InitChars(wchar_t* c)
13 {
14     for (size_t i = 0; i < 100; i++)
15     {
16         c[i] = L'a';
17     }
18     return sizeof(wchar_t) * 100;
19 }
View Code


    2.10、ExampleCore_5_9

Advanced .Net Debugging 7:託管堆與垃圾收集
 1 using System.Runtime.InteropServices;
 2 
 3 namespace ExampleCore_5_9
 4 {
 5     internal class Program
 6     {
 7         static void Main(string[] args)
 8         {
 9             Program program = new Program();
10             program.Run();
11         }
12 
13         public void Run()
14         {
15             Console.WriteLine("<alloc size>");
16             var sizeValue = Console.ReadLine();
17             if (!int.TryParse(sizeValue, out int size))
18             {
19                 Console.WriteLine("引數:<alloc size>!");
20                 return;
21             }
22 
23             Console.WriteLine("<max mem in MB>");
24             var maxMemValue = Console.ReadLine();
25             if (!int.TryParse(maxMemValue, out int maxMem))
26             {
27                 Console.WriteLine("引數:<max mem in MB>!");
28                 return;
29             }
30 
31             byte[][]? nonPinned = null;
32             byte[][]? pinned = null;
33             GCHandle[]? pinnedHandles = null;
34 
35             int numAllocs = maxMem * 1000000 / size;
36             pinnedHandles = new GCHandle[numAllocs];
37 
38             pinned = new byte[numAllocs / 2][];
39             nonPinned = new byte[numAllocs / 2][];
40 
41             for (int i = 0; i < numAllocs / 2; i++)
42             {
43                 nonPinned[i] = new byte[size];
44                 pinned[i] = new byte[size];
45                 pinnedHandles[i] = GCHandle.Alloc(pinned[i], GCHandleType.Pinned);
46             }
47 
48             Console.WriteLine("Press any key to GC & promo to gen1");
49             Console.ReadKey();
50 
51             GC.Collect();
52 
53             Console.WriteLine("Press any key to GC & promo to gen2");
54             Console.ReadKey();
55 
56             GC.Collect();
57 
58             Console.WriteLine("Press any key to GC(free non pinned)");
59             Console.ReadKey();
60 
61             for (int i = 0; i < numAllocs / 2; i++)
62             {
63                 nonPinned[i] = null;
64             }
65 
66             GC.Collect();
67 
68             Console.WriteLine("Press any key to Exit");
69             Console.ReadKey();
70         }
71     }
72 }
View Code


    2.11、ExampleCore_5_10

Advanced .Net Debugging 7:託管堆與垃圾收集
 1 using System.Xml.Serialization;
 2 
 3 namespace ExampleCore_5_10
 4 {
 5     public class Person
 6     {
 7         private string? _name;
 8         private string? _social;
 9         private int _age;
10 
11         public string? Name
12         {
13             get { return _name; }
14             set { _name = value; }
15         }
16 
17         public string? SocialSecurity
18         {
19             get { return _social; }
20             set { _social = value; }
21         }
22 
23         public int Age
24         {
25             get { return _age; }
26             set { _age = value; }
27         }
28 
29         public Person()
30         {
31 
32         }
33 
34         public Person(string name, string socialSecurity, int age)
35         {
36             Name = name;
37             SocialSecurity = socialSecurity;
38             Age = age;
39         }
40     }
41 
42     internal class Program
43     {
44         static void Main(string[] args)
45         {
46             Program program = new Program();
47             program.Run();
48             Console.WriteLine("Hello, World!");
49         }
50 
51         public void Run()
52         {
53             XmlRootAttribute xmlRoot = new XmlRootAttribute();
54             xmlRoot.ElementName = "MyPersonRoot";
55             xmlRoot.Namespace = "http://www.contoso.com";
56             xmlRoot.IsNullable = true;
57 
58             int index = 0;
59             while (true)
60             {
61 
62                 Person person = new Person();
63                 person.Name = "Mario Hewardt";
64                 person.SocialSecurity = "xxx-xx-xxxxxx";
65                 person.Age = 56;
66 
67                 XmlSerializer xmlSerializer = new XmlSerializer(typeof(Person), xmlRoot);
68                 Stream s = new FileStream("F:\\ser.txt", FileMode.Create);
69 
70                 xmlSerializer.Serialize(s, person);
71                 s.Close();
72 
73                 index++;
74 
75                 Console.WriteLine($"已經序列化第【{index}】個人了!");
76             }
77         }
78     }
79 }
View Code


三、基礎知識
    3.1、Windows 記憶體架構簡介

        A、基礎知識
            我們先上一張圖,來了解一下 Windows 的記憶體架構。
            

            在使用者態(User Mode)中執行的程序通常會使用一個或者多個堆管理器。最常見的堆管理器有兩個:Windows 堆管理器和 CLR 堆管理器。
            Windows 堆管理器:它負責滿足大多數的記憶體分配/回收的請求,它從 Windows 虛擬記憶體管理器中分配大塊記憶體空間(稱為記憶體段(Segment)),並且透過維持特定的記錄資料(旁氏列表(Look Aside List)和 空閒列表(Free List)),以一種高效的方式將大塊記憶體空間分割為許多更小的記憶體塊來滿足程序的分配需求。
            CLR 堆管理器:和 Windows 堆管理器的功能類似,它為託管程序中所有的記憶體分配/回收請求提供服務。它同樣也是從 Windows 虛擬記憶體管理器中分配大塊記憶體(也稱為記憶體段),使用這些記憶體段滿足託管系統的記憶體分配/回收的請求。
            這兩種堆管理器的差別就是維持堆完整性時使用的記錄資料的結構是不同的。

            CLR 堆管理器有兩種運作模式:第一種是工作站模式,第二種是伺服器模式
            伺服器模式的特點:它有多個堆,堆得數量是和處理器的數量是一致的,並且堆中記憶體段的大小是比工作站的記憶體段的大小要大的。
            從 GC 角度來看,兩種工作模式是有 GC 執行緒模型的差異,在伺服器模式下,有一個專門的執行緒管理所有的 GC 的操作,而工作站模式,在執行記憶體分配的執行緒上執行 GC 操作。

            堆按物件大小分類:大物件堆(LOH:Large Object Heap)和 小物件堆(SOH:Small Object Heap)。
            大物件堆(LOH):物件的大小等於或者大於 85000 byte 都會分配在大物件堆,>=85000,它有一個初始記憶體段,大小是16 MB。
            小物件堆(SOH):物件的大小是小於 85000 byte 都會分配在小物件堆上,<85000,它有一個初始記憶體段,大小是 16 MB。

            CLR 堆管理器運作模式和大小物件堆之間的關係如圖:
                工作站模式:
                  
                伺服器模式:

                  

            當小物件堆中的記憶體耗盡的時候,CLR 堆管理器就會觸發 GC,如果記憶體空間仍然不足時,將對堆進行擴充套件。如果大物件堆中的記憶體耗盡時,堆管理器將建立一個新的記憶體段來提供記憶體。

            如果我們想使用偵錯程式在託管堆中找到指定物件的分配的記憶體,可以使用【!DumpHeap】命令。預設情況下,該命令會列出在託管堆中的所有物件以及它們的地址、方法表和大小。
  
            【!DumpHeap】命令有一些開關選項,對於我們過濾資料很有幫助。如圖:
            
                     


            記憶體分配

              記憶體分配的過程很簡單,當然,這只是一個示意圖,示意圖很簡單,我也就不多說了,我相信大家也能很容易看得懂。如圖:
              

              CLR 堆管理器和 Window 堆管理器的記憶體分配是有差別的,CLR 堆管理器記憶體分配一定是緊跟在記憶體段中最後一個已分配的物件的後面,以此類推。Windows 堆管理器是不會這樣管理物件之間的位置的。在Windows 堆管理器中,當有一個記憶體分配的請求進來的時候,可以使用記憶體段中任何一塊空閒記憶體來滿足這個請求。
              有一點需要注意的是,當達到記憶體閾值的時候,將觸發 GC 來執行垃圾回收的動作。在這種情況下,會首先執行 GC,然後在嘗試滿足記憶體分配的請求。
              最後還有一點要注意,要判斷物件是否是可終結的,如果物件是可終結的,那麼將在 GC 中記錄這個物件,以便正確的管理物件的生命週期。


        B、眼見為實
            除錯原始碼:ExampleCore_5_1
            除錯任務:【!DumpHeap】命令的使用
            1)、NTSD 除錯
                編譯專案,開啟【Visual Studio 2022 Developer Command Prompt v17.9.4】,輸入命令【NTSD E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_5_1\bin\Debug\net8.0\ExampleCore_5_1.exe】,開啟偵錯程式。
                我們進入偵錯程式後,繼續使用【g】命令,執行偵錯程式,直到偵錯程式輸出如圖:
                

                此時,我們按【ctrl+c】組合鍵進入中斷模式。
                我們可以直接執行【!DumpHeap】命令,輸出內容不少。

  1 0:002> !DumpHeap
  2          Address               MT     Size
  3 000001c901400028 00007ff80e0d9df8       96
  4 000001c901400088 00007ff80e170a90      128
  5 000001c901400108 00007ff80e170b90      128
  6 000001c901400188 00007ff80e170c90      128
  7 000001c901400208 00007ff80e025fa8       24
  8 000001c901400220 00007ff80e1747c8       80
  9 000001c901400270 00007ff80e0d9df8       68
 10 000001c9014002b8 00007ff80e178ef8      288
 11 000001c9014003d8 000001c8fcefc160       24 Free
 12 000001c9014003f0 00007ff80e0d9df8     3120
 13 000001c901401020 00007ff80e17ad28       24
 14 000001c901401038 00007ff80e17bfc8       24
 15 000001c901401050 00007ff80e17bfc8       24
 16 000001c901401068 00007ff80e17c7d0       24
 17 000001c901401080 00007ff80e17c108       24
 18 000001c901401098 00007ff80e17ca50       24
 19 000001c9014010b0 00007ff80e0dec08       46
 20 000001c9014010e0 00007ff80e0dec08      202
 21 000001c9014011b0 00007ff80e0dec08       76
 22 000001c901401200 000001c8fcefc160       24 Free
 23 000001c901401218 00007ff80e0dec08    30224
 24 000001c901408828 00007ff80e0dec08       80
 25 000001c901408878 00007ff80e0dec08      142
 26 000001c901408908 00007ff80e0dec08       68
 27 000001c901408950 00007ff80e0dec08       74
 28 000001c9014089a0 00007ff80e0dec08      232
 29 000001c901408a88 00007ff80e0dec08       66
 30 000001c901408ad0 00007ff80e0dec08      468
 31 000001c901408ca8 00007ff80e0dec08       60
 32 000001c901408ce8 00007ff80e0dec08       58
 33 000001c901408d28 00007ff80e0dec08       36
 34 000001c901408d50 00007ff80e0dec08      160
 35 000001c901408df0 00007ff80e0dec08       32
 36 000001c901408e10 00007ff80e0dec08       64
 37 000001c901408e50 00007ff80e0dec08       48
 38 000001c901408e80 00007ff80e17fa90       32
 39 000001c901408ea0 00007ff80e17fa90       24
 40 000001c901408eb8 00007ff80e0dec08      274
 41 000001c901408fd0 00007ff80e1a21f0      400
 42 000001c901409160 00007ff80e1a2320       24
 43 000001c901409178 00007ff80e1a23f8      104
 44 000001c9014091e0 00007ff80e1a3020       32
 45 000001c901409200 00007ff80e1a2b10       64
 46 000001c901409240 00007ff80e1a3a08       88
 47 000001c901409298 00007ff80e1a3ce8       24
 48 000001c9014092b0 00007ff80e1a2b10       64
 49 000001c9014092f0 00007ff80e1a37a0       64
 50 000001c901409330 00007ff80e1a3ce8       24
 51 000001c901409348 00007ff80e1a62e8       32
 52 000001c901409368 00007ff80e1a71c8       40
 53 000001c901409390 00007ff80e1a85b8      184
 54 000001c901409448 00007ff80e1a2320       24
 55 000001c901409460 00007ff80e1a23f8      104
 56 000001c9014094c8 00007ff80e1a2b10       64
 57 000001c901409508 00007ff80e1a3a08       88
 58 000001c901409560 00007ff80e1a3ce8       24
 59 000001c901409578 00007ff80e1a2b10       64
 60 000001c9014095b8 00007ff80e1a37a0       64
 61 000001c9014095f8 00007ff80e1a3ce8       24
 62 000001c901409610 00007ff80e1a56e0       24
 63 000001c901409628 00007ff80e1a56e0       24
 64 000001c901409640 00007ff80e025fa8       24
 65 000001c901409658 00007ff80e0dec08       46
 66 000001c901409688 00007ff80e1d0278       48
 67 000001c9014096b8 00007ff80e1d1e90       48
 68 000001c9014096e8 00007ff80e1d24a8       24
 69 000001c901409700 00007ff80e1d2220       24
 70 000001c901409718 00007ff80e1d0c28       64
 71 000001c901409758 00007ff80e1d24a8       24
 72 000001c901409770 00007ff80e1d2220       24
 73 000001c901409788 00007ff80e0dec08       46
 74 000001c9014097b8 00007ff80e0dec08       46
 75 000001c9014097e8 00007ff80e1d31c8       48
 76 000001c901409818 00007ff80e0dec08       46
 77 000001c901409848 00007ff80e1d96e8       24
 78 000001c901409860 00007ff80e1d9218      104
 79 000001c9014098c8 00007ff80e025fa8       24
 80 000001c9014098e0 00007ff80e1dcea0       40
 81 000001c901409908 00007ff80e1dddf0       72
 82 000001c901409950 00007ff80e1dc008       64
 83 000001c901409990 00007ff80e1de1f8       24
 84 000001c9014099a8 00007ff80e1de618       40
 85 000001c9014099d0 00007ff80e1de958       28
 86 000001c9014099f0 00007ff80e1f13b0       48
 87 000001c901409a20 00007ff80e1de958      536
 88 000001c901409c38 00007ff80e1f3530       48
 89 000001c901409c68 00007ff80e0dec08       46
 90 000001c901409c98 00007ff80e025fa8       24
 91 000001c901409cb0 00007ff80e0dec08       46
 92 0000020993620008 00007ff80e0dec08       24
 93 0000020993620020 00007ff80e02a318       40
 94 0000020993620048 00007ff80e0dec08      150
 95 00000209936200e0 00007ff80e0dec08      122
 96 0000020993620160 00007ff80e0dec08       42
 97 0000020993620190 00007ff80e0dec08       30
 98 00000209936201b0 00007ff80e0dec08       50
 99 00000209936201e8 00007ff80e0dec08       38
100 0000020993620210 00007ff80e0dec08       26
101 0000020993620230 00007ff80e0dec08       34
102 0000020993620258 00007ff80e0dec08      118
103 00000209936202d0 00007ff80e0dec08      126
104 0000020993620350 00007ff80e1a1800       32
105 0000020993620370 00007ff80e0dec08       30
106 0000020993620390 00007ff80e0dec08       32
107 00000209936203b0 00007ff80e0dec08       84
108 0000020993620408 00007ff80e0dec08       98
109 0000020993620470 00007ff80e0dec08       48
110 00000209936204a0 00007ff80e0dec08       86
111 00000209936204f8 00007ff80e0dec08       32
112 0000020993620518 00007ff80e0dec08       36
113 0000020993620540 00007ff80e0dec08       64
114 0000020993620580 00007ff80e0dec08      112
115 00000209936205f0 00007ff80e0dec08       38
116 0000020993620618 00007ff80e0dec08       90
117 0000020993620678 00007ff80e0dec08       42
118 00000209936206a8 00007ff80e0dec08      104
119 0000020993620710 00007ff80e0dec08      130
120 0000020993620798 00007ff80e0dec08      160
121 0000020993620838 00007ff80e0dec08      124
122 00000209936208b8 00007ff80e0dec08       38
123 00000209936208e0 00007ff80e0dec08       24
124 00000209936208f8 00007ff80e0dec08       44
125 0000020993620928 00007ff80e0dec08       24
126 0000020993620940 00007ff80e1d9ff8       24
127 0000020993620958 00007ff80e0dec08       26
128 0000020993620978 00007ff80e0dec08       42
129 00000209936209a8 00007ff80e0dec08       40
130 00000209936209d0 00007ff80e0dec08       32
131 00000209936209f0 00007ff80e0dec08       32
132 0000020993620a10 00007ff80e0dec08       40
133 0000020993620a38 00007ff80e0dec08       34
134 0000020993620a60 00007ff80e1adfe0       40
135 000001c8fec00028 00007ff80e02c4d8     8184
136 000001c8fec02020 00007ff80e02c4d8     8184
137 
138 Statistics:
139               MT    Count    TotalSize Class Name
140 00007ff80e1de1f8        1           24 System.Threading.Tasks.Task+<>c
141 00007ff80e1d9ff8        1           24 System.Byte[]
142 00007ff80e1d96e8        1           24 System.IO.Stream+NullStream
143 00007ff80e17ca50        1           24 System.OrdinalIgnoreCaseComparer
144 00007ff80e17c7d0        1           24 System.OrdinalCaseSensitiveComparer
145 00007ff80e17c108        1           24 System.Collections.Generic.NonRandomizedStringEqualityComparer+OrdinalIgnoreCaseComparer
146 00007ff80e17ad28        1           24 System.Collections.Generic.GenericEqualityComparer`1[[System.String, System.Private.CoreLib]]
147 00007ff80e1a62e8        1           32 System.Collections.Generic.List`1[[System.WeakReference`1[[System.Diagnostics.Tracing.EventSource, System.Private.CoreLib]], System.Private.CoreLib]]
148 00007ff80e1a3020        1           32 System.Diagnostics.Tracing.ActivityTracker
149 00007ff80e1a1800        1           32 System.Guid
150 00007ff80e1de618        1           40 System.IO.TextWriter+NullTextWriter
151 00007ff80e1dcea0        1           40 System.Threading.Tasks.TaskFactory
152 00007ff80e1adfe0        1           40 Interop+INPUT_RECORD
153 00007ff80e1a71c8        1           40 System.WeakReference`1[[System.Diagnostics.Tracing.EventSource, System.Private.CoreLib]][]
154 00007ff80e02a318        1           40 System.RuntimeType
155 00007ff80e1f3530        1           48 System.IO.TextWriter+SyncTextWriter
156 00007ff80e1f13b0        1           48 System.Text.OSEncoder
157 00007ff80e1d31c8        1           48 System.ConsolePal+WindowsConsoleStream
158 00007ff80e1d24a8        2           48 System.Text.EncoderReplacementFallback
159 00007ff80e1d2220        2           48 System.Text.DecoderReplacementFallback
160 00007ff80e1d1e90        1           48 System.Text.UTF8Encoding+UTF8EncodingSealed
161 00007ff80e1d0278        1           48 System.Reflection.RuntimeAssembly
162 00007ff80e1a56e0        2           48 System.WeakReference`1[[System.Diagnostics.Tracing.EventSource, System.Private.CoreLib]]
163 00007ff80e1a2320        2           48 System.Diagnostics.Tracing.TraceLoggingEventHandleTable
164 00007ff80e17bfc8        2           48 System.Collections.Generic.NonRandomizedStringEqualityComparer+OrdinalComparer
165 000001c8fcefc160        2           48      Free
166 00007ff80e17fa90        2           56 System.String[]
167 00007ff80e1dc008        1           64 System.Threading.ContextCallback
168 00007ff80e1d0c28        1           64 System.Text.OSEncoding
169 00007ff80e1dddf0        1           72 System.Threading.Tasks.Task`1[[System.Threading.Tasks.VoidTaskResult, System.Private.CoreLib]]
170 00007ff80e1747c8        1           80 System.Collections.Generic.Dictionary`2[[System.String, System.Private.CoreLib],[System.Object, System.Private.CoreLib]]
171 00007ff80e1a3ce8        4           96 System.WeakReference`1[[System.Diagnostics.Tracing.EventProvider, System.Private.CoreLib]]
172 00007ff80e025fa8        4           96 System.Object
173 00007ff80e1d9218        1          104 System.IO.StreamWriter
174 00007ff80e1a37a0        2          128 System.Diagnostics.Tracing.EventPipeEventProvider
175 00007ff80e170c90        1          128 System.ExecutionEngineException
176 00007ff80e170b90        1          128 System.StackOverflowException
177 00007ff80e170a90        1          128 System.OutOfMemoryException
178 00007ff80e1a3a08        2          176 System.Diagnostics.Tracing.EtwEventProvider
179 00007ff80e1a85b8        1          184 System.Diagnostics.Tracing.NativeRuntimeEventSource
180 00007ff80e1a23f8        2          208 System.IntPtr[]
181 00007ff80e1a2b10        4          256 System.Diagnostics.Tracing.EventSource+OverrideEventProvider
182 00007ff80e178ef8        1          288 System.Collections.Generic.Dictionary`2+Entry[[System.String, System.Private.CoreLib],[System.Object, System.Private.CoreLib]][]
183 00007ff80e1a21f0        1          400 System.Diagnostics.Tracing.RuntimeEventSource
184 00007ff80e1de958        2          564 System.Char[]
185 00007ff80e0d9df8        3         3284 System.Int32[]
186 00007ff80e02c4d8        2        16368 System.Object[]
187 00007ff80e0dec08       64        35132 System.String

                輸出包含兩個部分,第一部分輸出了在當前託管堆中所有的物件,包括物件的地址、方法表和大小。我們有了物件的地址,就可以使用【!DumpObj】命令檢視物件的詳情。

1 0:002> !DumpObj 000001c901400028
2 Name:        System.Int32[]
3 MethodTable: 00007ff80e0d9df8
4 EEClass:     00007ff80e0d9d78
5 Tracked Type: false
6 Size:        96(0x60) bytes
7 Array:       Rank 1, Number of elements 18, Type Int32
8 Fields:

                【!DumpObj】命令還有一個變體就是【!do】,輸出結果是一樣的。

1 0:002> !do 000001c901400028
2 Name:        System.Int32[]
3 MethodTable: 00007ff80e0d9df8
4 EEClass:     00007ff80e0d9d78
5 Tracked Type: false
6 Size:        96(0x60) bytes
7 Array:       Rank 1, Number of elements 18, Type Int32
8 Fields:
9 None

                【!DumpHeap】命令輸出的第二部分包含了有關託管堆行為的統計資訊,其中相關的物件被分為一組,給出了這組物件的方法表地址、例項個數、總體大小和物件的型別名稱。
                【!DumpHeap】命令不跟任何引數,輸出內容太多,我們可以使用它的命令開關進行過濾,比如:-type、-mt 還有很多,可以自己去嘗試。-type 可以在託管堆上查詢指定的型別名,-mt 可以查詢指定的方法表。
                我們使用【!DumpHeap -type ExampleCore_5_1.Name】命令,查詢 Name 型別。

1 0:002> !DumpHeap -type ExampleCore_5_1_1.Name
2          Address               MT     Size
3 
4 Statistics:
5               MT    Count    TotalSize Class Name
6 Total 0 objects

                什麼也沒輸出,因為在託管堆上還沒有該物件。
                此時,我們繼續執行【g】偵錯程式,直到偵錯程式輸出如圖:
                

                我們繼續按【ctrl+c】組合鍵,進入中斷模式,再次執行【!DumpHeap -type ExampleCore_5_1.Name】命令,這次輸出不同了。

1 0:002> !DumpHeap -type ExampleCore_5_1.Name
2          Address               MT     Size
3 000001c901409ce0 00007ff80e1a9418       32
4 
5 Statistics:
6               MT    Count    TotalSize Class Name
7 00007ff80e1a9418        1           32 ExampleCore_5_1.Name
8 Total 1 objects

                輸出的結果和【!DumpHeap】命令的預設輸出類似,首相給出特定例項的特定資料(地址、方法表和大小),最後就是統計資訊,在託管堆上只有一個這樣的例項、大小、型別名和方法表的地址。


            2)、Windbg Preview 除錯
                編譯專案,開啟【Windbg Preview】偵錯程式,依次點選【檔案】---【Launch executable】,載入我們的專案檔案:ExampleCore_5_1.exe。進入偵錯程式,我們使用【g】命令,繼續執行偵錯程式,直到我們的控制檯程式輸出“Press any key to allocate memory”這樣的文字,點選偵錯程式中【break】按鈕,中斷偵錯程式的執行。
                此時,我們就可以執行【!DumpHeap】命令,檢視一下託管堆上有什麼東西,內容還是不少的。

  1 0:001> !DumpHeap
  2          Address               MT           Size
  3     025e5a800028     7ff80653c4d8          8,184 
  4     025e5a802020     7ff80653c4d8          8,184 
  5     025e5d000028     7ff8065e9df8             96 
  6     025e5d000088     7ff806680a90            128 
  7     025e5d000108     7ff806680b90            128 
  8     025e5d000188     7ff806680c90            128 
  9     025e5d000208     7ff806535fa8             24 
 10     025e5d000220     7ff8066847c8             80 
 11     025e5d000270     7ff8065e9df8             68 
 12     025e5d0002b8     7ff806688ef8            288 
 13     025e5d0003d8     025e588f79b0             24 Free
 14     025e5d0003f0     7ff8065e9df8          3,120 
 15     025e5d001020     7ff80668ad28             24 
 16     025e5d001038     7ff80668bfc8             24 
 17     025e5d001050     7ff80668bfc8             24 
 18     025e5d001068     7ff80668c7d0             24 
 19     025e5d001080     7ff80668c108             24 
 20     025e5d001098     7ff80668ca50             24 
 21     025e5d0010b0     7ff8065eec08             46 
 22     025e5d0010e0     7ff8065eec08            202 
 23     025e5d0011b0     7ff8065eec08             76 
 24     025e5d001200     025e588f79b0             24 Free
 25     025e5d001218     7ff8065eec08         30,224 
 26     025e5d008828     7ff8065eec08             80 
 27     025e5d008878     7ff8065eec08            142 
 28     025e5d008908     7ff8065eec08             68 
 29     025e5d008950     7ff8065eec08             74 
 30     025e5d0089a0     7ff8065eec08            232 
 31     025e5d008a88     7ff8065eec08             66 
 32     025e5d008ad0     7ff8065eec08            468 
 33     025e5d008ca8     7ff8065eec08             60 
 34     025e5d008ce8     7ff8065eec08             58 
 35     025e5d008d28     7ff8065eec08             36 
 36     025e5d008d50     7ff8065eec08            160 
 37     025e5d008df0     7ff8065eec08             32 
 38     025e5d008e10     7ff8065eec08             64 
 39     025e5d008e50     7ff8065eec08             48 
 40     025e5d008e80     7ff80668fa90             32 
 41     025e5d008ea0     7ff80668fa90             24 
 42     025e5d008eb8     7ff8065eec08            274 
 43     025e5d008fd0     7ff8066b21f0            400 
 44     025e5d009160     7ff8066b2320             24 
 45     025e5d009178     7ff8066b23f8            104 
 46     025e5d0091e0     7ff8066b3020             32 
 47     025e5d009200     7ff8066b2b10             64 
 48     025e5d009240     7ff8066b3a08             88 
 49     025e5d009298     7ff8066b3ce8             24 
 50     025e5d0092b0     7ff8066b2b10             64 
 51     025e5d0092f0     7ff8066b37a0             64 
 52     025e5d009330     7ff8066b3ce8             24 
 53     025e5d009348     7ff8066b62e8             32 
 54     025e5d009368     7ff8066b71c8             40 
 55     025e5d009390     7ff8066b85b8            184 
 56     025e5d009448     7ff8066b2320             24 
 57     025e5d009460     7ff8066b23f8            104 
 58     025e5d0094c8     7ff8066b2b10             64 
 59     025e5d009508     7ff8066b3a08             88 
 60     025e5d009560     7ff8066b3ce8             24 
 61     025e5d009578     7ff8066b2b10             64 
 62     025e5d0095b8     7ff8066b37a0             64 
 63     025e5d0095f8     7ff8066b3ce8             24 
 64     025e5d009610     7ff8066b56e0             24 
 65     025e5d009628     7ff8066b56e0             24 
 66     025e5d009640     7ff806535fa8             24 
 67     025e5d009658     7ff8065eec08             46 
 68     025e5d009688     7ff8066e0278             48 
 69     025e5d0096b8     7ff8066e1e90             48 
 70     025e5d0096e8     7ff8066e24a8             24 
 71     025e5d009700     7ff8066e2220             24 
 72     025e5d009718     7ff8066e0c28             64 
 73     025e5d009758     7ff8066e24a8             24 
 74     025e5d009770     7ff8066e2220             24 
 75     025e5d009788     7ff8065eec08             46 
 76     025e5d0097b8     7ff8065eec08             46 
 77     025e5d0097e8     7ff8066e31c8             48 
 78     025e5d009818     7ff8065eec08             46 
 79     025e5d009848     7ff8066e96e8             24 
 80     025e5d009860     7ff8066e9218            104 
 81     025e5d0098c8     7ff806535fa8             24 
 82     025e5d0098e0     7ff8066ecea0             40 
 83     025e5d009908     7ff8066eddf0             72 
 84     025e5d009950     7ff8066ec008             64 
 85     025e5d009990     7ff8066ee1f8             24 
 86     025e5d0099a8     7ff8066ee618             40 
 87     025e5d0099d0     7ff8066ee958             28 
 88     025e5d0099f0     7ff8067013b0             48 
 89     025e5d009a20     7ff8066ee958            536 
 90     025e5d009c38     7ff806703530             48 
 91     025e5d009c68     7ff8065eec08             46 
 92     025e5d009c98     7ff806535fa8             24 
 93     025e5d009cb0     7ff8065eec08             46 
 94     029eeef70008     7ff8065eec08             24 
 95     029eeef70020     7ff80653a318             40 
 96     029eeef70048     7ff8065eec08            150 
 97     029eeef700e0     7ff8065eec08            122 
 98     029eeef70160     7ff8065eec08             42 
 99     029eeef70190     7ff8065eec08             30 
100     029eeef701b0     7ff8065eec08             50 
101     029eeef701e8     7ff8065eec08             38 
102     029eeef70210     7ff8065eec08             26 
103     029eeef70230     7ff8065eec08             34 
104     029eeef70258     7ff8065eec08            118 
105     029eeef702d0     7ff8065eec08            126 
106     029eeef70350     7ff8066b1800             32 
107     029eeef70370     7ff8065eec08             30 
108     029eeef70390     7ff8065eec08             32 
109     029eeef703b0     7ff8065eec08             84 
110     029eeef70408     7ff8065eec08             98 
111     029eeef70470     7ff8065eec08             48 
112     029eeef704a0     7ff8065eec08             86 
113     029eeef704f8     7ff8065eec08             32 
114     029eeef70518     7ff8065eec08             36 
115     029eeef70540     7ff8065eec08             64 
116     029eeef70580     7ff8065eec08            112 
117     029eeef705f0     7ff8065eec08             38 
118     029eeef70618     7ff8065eec08             90 
119     029eeef70678     7ff8065eec08             42 
120     029eeef706a8     7ff8065eec08            104 
121     029eeef70710     7ff8065eec08            130 
122     029eeef70798     7ff8065eec08            160 
123     029eeef70838     7ff8065eec08            124 
124     029eeef708b8     7ff8065eec08             38 
125     029eeef708e0     7ff8065eec08             24 
126     029eeef708f8     7ff8065eec08             44 
127     029eeef70928     7ff8065eec08             24 
128     029eeef70940     7ff8066e9ff8             24 
129     029eeef70958     7ff8065eec08             26 
130     029eeef70978     7ff8065eec08             42 
131     029eeef709a8     7ff8065eec08             40 
132     029eeef709d0     7ff8065eec08             32 
133     029eeef709f0     7ff8065eec08             32 
134     029eeef70a10     7ff8065eec08             40 
135     029eeef70a38     7ff8065eec08             34 
136     029eeef70a60     7ff8066bdfe0             40 
137 
138 Statistics:
139           MT Count TotalSize Class Name
140 7ff80668ad28     1        24 System.Collections.Generic.GenericEqualityComparer<System.String>
141 7ff80668c7d0     1        24 System.OrdinalCaseSensitiveComparer
142 7ff80668c108     1        24 System.Collections.Generic.NonRandomizedStringEqualityComparer+OrdinalIgnoreCaseComparer
143 7ff80668ca50     1        24 System.OrdinalIgnoreCaseComparer
144 7ff8066e96e8     1        24 System.IO.Stream+NullStream
145 7ff8066ee1f8     1        24 System.Threading.Tasks.Task+<>c
146 7ff8066e9ff8     1        24 System.Byte[]
147 7ff8066b3020     1        32 System.Diagnostics.Tracing.ActivityTracker
148 7ff8066b62e8     1        32 System.Collections.Generic.List<System.WeakReference<System.Diagnostics.Tracing.EventSource>>
149 7ff8066b1800     1        32 System.Guid
150 7ff8066b71c8     1        40 System.WeakReference<System.Diagnostics.Tracing.EventSource>[]
151 7ff8066ecea0     1        40 System.Threading.Tasks.TaskFactory
152 7ff8066ee618     1        40 System.IO.TextWriter+NullTextWriter
153 7ff80653a318     1        40 System.RuntimeType
154 7ff8066bdfe0     1        40 Interop+INPUT_RECORD
155 025e588f79b0     2        48 Free
156 7ff80668bfc8     2        48 System.Collections.Generic.NonRandomizedStringEqualityComparer+OrdinalComparer
157 7ff8066b2320     2        48 System.Diagnostics.Tracing.TraceLoggingEventHandleTable
158 7ff8066b56e0     2        48 System.WeakReference<System.Diagnostics.Tracing.EventSource>
159 7ff8066e0278     1        48 System.Reflection.RuntimeAssembly
160 7ff8066e1e90     1        48 System.Text.UTF8Encoding+UTF8EncodingSealed
161 7ff8066e24a8     2        48 System.Text.EncoderReplacementFallback
162 7ff8066e2220     2        48 System.Text.DecoderReplacementFallback
163 7ff8066e31c8     1        48 System.ConsolePal+WindowsConsoleStream
164 7ff8067013b0     1        48 System.Text.OSEncoder
165 7ff806703530     1        48 System.IO.TextWriter+SyncTextWriter
166 7ff80668fa90     2        56 System.String[]
167 7ff8066e0c28     1        64 System.Text.OSEncoding
168 7ff8066ec008     1        64 System.Threading.ContextCallback
169 7ff8066eddf0     1        72 System.Threading.Tasks.Task<System.Threading.Tasks.VoidTaskResult>
170 7ff8066847c8     1        80 System.Collections.Generic.Dictionary<System.String, System.Object>
171 7ff806535fa8     4        96 System.Object
172 7ff8066b3ce8     4        96 System.WeakReference<System.Diagnostics.Tracing.EventProvider>
173 7ff8066e9218     1       104 System.IO.StreamWriter
174 7ff806680a90     1       128 System.OutOfMemoryException
175 7ff806680b90     1       128 System.StackOverflowException
176 7ff806680c90     1       128 System.ExecutionEngineException
177 7ff8066b37a0     2       128 System.Diagnostics.Tracing.EventPipeEventProvider
178 7ff8066b3a08     2       176 System.Diagnostics.Tracing.EtwEventProvider
179 7ff8066b85b8     1       184 System.Diagnostics.Tracing.NativeRuntimeEventSource
180 7ff8066b23f8     2       208 System.IntPtr[]
181 7ff8066b2b10     4       256 System.Diagnostics.Tracing.EventSource+OverrideEventProvider
182 7ff806688ef8     1       288 System.Collections.Generic.Dictionary<System.String, System.Object>+Entry[]
183 7ff8066b21f0     1       400 System.Diagnostics.Tracing.RuntimeEventSource
184 7ff8066ee958     2       564 System.Char[]
185 7ff8065e9df8     3     3,284 System.Int32[]
186 7ff80653c4d8     2    16,368 System.Object[]
187 7ff8065eec08    64    35,132 System.String
188 Total 134 objects, 58,996 bytes

                為了讓大家看的更清楚,我沒有省略,如果是第一次檢視,完全的更好的,能有一個更直觀的感受。
                【!DumpHeap】命令的輸出分為兩個部分。第一個部分包含了位於當前託管堆中的所有物件。對於任何一個物件,都可以使用【!DumpObj】命令或者【!do】檢視物件的詳情。
                第二部分包含了託管堆行為的統計資訊,其中相關的物件會被分為一組,並給出了這組物件的方法表、物件數量、總體大小和物件的型別名。如圖:
                

                表示物件是 System.Collections.Generic.GenericEqualityComparer<System.String> 型別,方法表位於 7ff80668ad28,在託管堆中共有1個例項,總大小為 24 個位元組。
                在分析一個很大的託管堆以及需要找出哪些物件導致了堆空間的增長時,這些統計資訊非常有用。

                【!DumpHeap】命令不跟任何引數,輸出的內容太多了,如果我們想找一些特定的資訊就比較難。該命令提供了許多開關選項可以幫助我們。-type、-mt 這兩個開關可以幫助我們在託管堆中查詢指定的型別名或者方法表的地址。

1 0:001> !DumpHeap -type ExampleCore_5_1.Name

                我們執行該命令,沒有任何輸出,因為在當前的託管堆中沒有分配該型別的物件。我們【g】繼續執行偵錯程式,直到我們的控制檯程式輸出“Press any key to Exit”時,再次點選【break】按鈕,中斷偵錯程式的執行,再次執行【!DumpHeap -type ExampleCore_5_1.Name】命令。

1 0:001> !DumpHeap -type ExampleCore_5_1.Name
2          Address               MT           Size
3     025e5d009ce0     7ff8066b9418             32 
4 
5 Statistics:
6           MT Count TotalSize Class Name
7 7ff8066b9418     1        32 ExampleCore_5_1.Name
8 Total 1 objects, 32 bytes

                有了輸出內容了。這個結果和【!DumpHeap】命令的預設輸出是一致的。首先給出這例項的特定資料(地址、方法表和大小),然後是統計資訊,指出在託管堆中只有一個這種型別的例項。


    3.2、垃圾收集器內部工作機制
        CLR 的 GC 是一個高效的、可伸縮的以及可靠的自動記憶體管理器。在設計和實現 GC 之前,是遵循一些假設的。
        I、如果沒有特殊宣告,所有物件都是垃圾。這意味著,除非特別宣告,否則 GC 會收集託管堆上所有的物件。從本質來看,它為系統中所有活躍的物件都實現了一種引用跟蹤的模式,如果一個物件沒有任何引用,就可以認為是垃圾,就可以被回收。
        II、假設託管堆上的所有物件的活躍時間都是很短暫的。這種假設基於:如果一個物件活躍了一段時間了,那麼它很可能在更長一段時間內也是活躍的,因此不需要再次收集這個物件。
        III、透過代的概念跟蹤物件的持續時間。活躍時間短的物件歸為 0 代,而活躍時間更長的物件則歸為第 1 代和第 2 代。物件的活躍時間增長,其相應的代也會遞增。
        基於以上,我們可以得出一個定義:GC 是一個基於引用跟蹤的垃圾收集器。

        3.2.1、代
            A、基礎知識
                CLR GC 定義了 3 個級別的代,分別是:0 代、1代、2代。一個物件可以從某一代移到下一代,並且每個代的回收頻率也是不一樣的,0 代回收的最頻繁,2代回收的最少。我們最新建立的物件,一般都會儲存到 0 代。
                我們先上一個圖,說一下 GC 垃圾回收演算法是怎麼回事。
                
                    

                每代都有預定義的空間容量。當新的記憶體分配請求到來的時候,並且第 0 代已無法再容納新的物件時,也就是超過了第 0 代預定義的空間容量,就會啟動 GC,執行垃圾回收的操作 。GC 就會回收掉沒有任何根引用的物件(也就是垃圾物件),並且將所有帶有根引用的物件升級到第 1 代。如果將第 0 代保留下來的物件提升到第 1 代時,超過了第 1 代預定空間容量,那麼 GC 將在第 1 代回收沒有根引用的物件,並將有根引用的物件升級到第 2 代。如果將物件從第 1 代升級到第 2 代,導致第 2 代的預定空間容量不足,此時 CLR 堆管理器就會嘗試分配另一個記憶體段來容納第 2 代中的物件。如果在建立新的記憶體段失敗了,就會丟擲一個 OutOfMemoryException 異常。
                如果記憶體段不再使用,CLR 堆管理器將釋放它們。
                如果我們想理解物件具體在哪個代,那我們必須理解託管堆的記憶體段和代之間的關係。每個託管堆都包含了一個或者多個記憶體段用來容納物件。而且,在這些記憶體段中有一部分空間是專門用來儲存指定的代。來一張圖說明一下託管堆的記憶體段是怎麼回事。
                

                在這張圖中,託管堆的記憶體段被劃分為 3 個部分,分別存放不同代的物件,其中每個部分都有自己的起始地址,這個地址由 CLR 堆管理器來管理。第 0 代和第 1 代屬於同一個記憶體段,這個記憶體段被稱為臨時記憶體段(ephemeral segment),它儲存短暫活躍的物件。
                由於 GC 假設大多數物件都是短暫活躍的,因此GC 認為大多數物件的活躍時間都不會超過第 0 代,最多不超過第 1 代。位於第 2 代的物件是存活時間最長的物件,他們被收集的頻率也會最低。當然,第 2 代物件也有可能儲存在臨時記憶體段中。透過檢視物件的地址和了解存放每代物件的地址範圍,我們就會很容易找到指定物件屬於哪一代。
                如果我們想檢視代的資訊,可以使用 【!eeheap】命令,這個命令可以輸出與 GC 和載入器相關的全部資訊,如果我們只是想輸出 GC 的資訊,可以使用【!eeheap -gc】命令。

                注意1:由於我們是 .NET 8.0 的環境,不能使用 SOSEX 擴充套件裡面的命令,如果是 .NET Framework ,我們就可以使用【!dumpgen】命令檢視和代有關的物件,特別方便。【!dumpgen 0】表示檢視第 0 代的所有物件,以此類推。

                注意2:GC.Collect()作用是什麼?
                GC.Collect()本身所實現的功能比其字面意思表示的功能要多得多。它能強制觸發一次垃圾回收的操作,而不管實際上是否需要垃圾收集。這句話的後半部分很重要,“。。。而不管實際上是否需要垃圾收集”。在應用程式執行期間,GC 可以不斷的自我調節,以確保在應用程式的環境中表現出最優的行為。然而,透過 GC.Collect() 強制執行垃圾收集,有可能會破壞 GC 的自我微調演算法。因此,在通常情況下,我們強烈建議不使用這個 API。

            B、眼見為實
                除錯原始碼:ExampleCore_5_2
                除錯任務:證明 GC 透過代管理物件
                1)、NTSD 除錯
                    編譯專案,開啟【Visual Studio 2022 Developer Command Prompt v17.9.4】命令列工具,輸入命令【NTSD E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_5_2\bin\Debug\net8.0\ExampleCore_5_2.exe】,開啟【NTSD】偵錯程式。
                    我們直接【g】執行偵錯程式,直到偵錯程式輸出“Press any key to invoke GC”內容,如圖:
                    

                    我們按【ctrl+c】組合鍵,進入中斷模式。由於是我們主動中斷的,在執行相關棧操作,必須先切換到託管主執行緒。

1 0:003> ~0s
2 ntdll!NtDeviceIoControlFile+0x14:
3 00007fff`cc08d0c4 c3              ret
4 0:000>

                    我們使用【!ClrStack -a】命令檢視託管執行緒的呼叫棧,並顯示區域性變數和引數。

 1 0:000> !ClrStack -a
 2 OS Thread Id: 0x3928 (0)
 3         Child SP               IP Call Site
 4 000000511E3FE448 00007fffcc08d0c4 [InlinedCallFrame: 000000511e3fe448]
 5 000000511E3FE448 00007fffb093787a [InlinedCallFrame: 000000511e3fe448]
 6 000000511E3FE420 00007FFFB093787A Interop+Kernel32.ReadConsoleInput(IntPtr, INPUT_RECORD ByRef, Int32, Int32 ByRef)
 7     PARAMETERS:
 8         hConsoleInput = <no data>
 9         buffer = <no data>
10         numInputRecords_UseOne = <no data>
11         numEventsRead = <no data>
12     LOCALS:
13         <no data>
14         <no data>
15         <no data>
16         <no data>
17         <no data>
18         <no data>
19         <no data>
20 
21 000000511E3FE510 00007FFFB093AA0A System.ConsolePal.ReadKey(Boolean)
22     PARAMETERS:
23         intercept (<CLR reg>) = 0x0000000000000000
24     LOCALS:
25         <no data>
26         <no data>
27         <no data>
28         <no data>
29         <no data>
30         <no data>
31         <no data>
32         0x000000511E3FE550 = 0x00000238e5409cd8
33         <no data>
34         <no data>
35         <no data>
36         <no data>
37         <no data>
38 
39 000000511E3FE5D0 00007FFEB11719F9 ExampleCore_5_2.Program.Main(System.String[])
40     PARAMETERS:(表示是 Main() 方法的引數)
41         args (0x000000511E3FE650) = 0x00000238e5408ea0
42     LOCALS:(以下內容表示 Main() 方法的區域性變數)
43         0x000000511E3FE638 = 0x00000238e5409640
44         0x000000511E3FE630 = 0x00000238e5409660
45 
46 0:000>

                    這兩個物件在託管堆上的地址分別是 0x00000238e54096400x00000238e5409660,這兩個地址,我們可以使用【!DumpObj】或者【!do】命令,後跟物件的地址,就可以檢視它們的詳情。

 1 0:000> !DumpObj 0x00000238e5409640
 2 Name:        ExampleCore_5_2.Name
 3 MethodTable: 00007ffeb1229418
 4 EEClass:     00007ffeb1231f18
 5 Tracked Type: false
 6 Size:        32(0x20) bytes
 7 File:        E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_5_2\bin\Debug\net8.0\ExampleCore_5_2.dll
 8 Fields:
 9               MT    Field   Offset                 Type VT     Attr            Value Name
10 00007ffeb115ec08  4000001        8        System.String  0 instance 00000279775204a0 _first
11 00007ffeb115ec08  4000002       10        System.String  0 instance 00000279775204c0 _last
12 
13 0:000> !do 0x00000238e5409660
14 Name:        ExampleCore_5_2.Name
15 MethodTable: 00007ffeb1229418
16 EEClass:     00007ffeb1231f18
17 Tracked Type: false
18 Size:        32(0x20) bytes
19 File:        E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_5_2\bin\Debug\net8.0\ExampleCore_5_2.dll
20 Fields:
21               MT    Field   Offset                 Type VT     Attr            Value Name
22 00007ffeb115ec08  4000001        8        System.String  0 instance 00000279775204e8 _first
23 00007ffeb115ec08  4000002       10        System.String  0 instance 00000279775204c0 _last
24 0:000>

                    有了物件的地址,我們再使用【!eeheap -gc】命令,檢視 GC 的詳情,包含每個代具體的資訊,包括每個代的起始地址。

 1 0:000> !eeheap -gc
 2 Number of GC Heaps: 1
 3 generation 0 starts at 0x00000238E5400028 (第 0 代)
 4 generation 1 starts at 0x00000238E5000028 (第 1 代)
 5 generation 2 starts at 0x0000027977520008 (第 2 代)
 6 ephemeral segment allocation context: none
 7          segment             begin         allocated         committed    allocated size    committed size
 8 generation 0:   (第 0 代起始地址)
 9 00000278F6A1F320  00000238E5400028  00000238E5400028  00000238E5411000  0x0(0)  0x10fd8(69592)
10 generation 1:    (第 1 代起始地址)
11 00000278F6A1F270  00000238E5000028  00000238E5000028  00000238E5001000  0x0(0)  0xfd8(4056)
12 generation 2:     (第 2 代起始地址)
13 00000238E0EEE240  0000027977520008  0000027977520AD8  0000027977530000  0xad0(2768)  0xfff8(65528)
14 00000278F6A1F1C0  00000238E4C00028  00000238E4C00028  00000238E4C01000  0x0(0)  0xfd8(4056)
15 Large object heap starts at 0x0000000000000000
16          segment             begin         allocated         committed    allocated size    committed size
17 00000278F6A1F3D0  00000238E5800028  00000238E5800028  00000238E5801000  0x0(0)  0xfd8(4056)
18 Pinned object heap starts at 0x0000000000000000
19 00000278F6A1EC40  00000238E2C00028  00000238E2C04018  00000238E2C11000  0x3ff0(16368)  0x10fd8(69592)
20 Total Allocated Size:              Size: 0x4ac0 (19136) bytes.
21 Total Committed Size:              Size: 0x23f58 (147288) bytes.
22 ------------------------------
23 GC Allocated Heap Size:    Size: 0x4ac0 (19136) bytes.
24 GC Committed Heap Size:    Size: 0x23f58 (147288) bytes.

                    兩個區域性變數的地址分別是:0x00000238e5409640 和 0x00000238e5409660,有了物件的地址,我們再和每一代的起始地址進行比較,就可以得到答案。只需比較地址高位地址的前6-8位就可以。第 0 代地址前面部分都是 00000238E54 ,我們物件的地址也是以 00000238e54 開頭的,其他的就可以不用看了,說明 n1 和 n2 都在第 0 代。
                    我們繼續【g】執行偵錯程式,直到偵錯程式輸出“Press any key to invoke GC”字樣,如圖:
                    

                    繼續按組合鍵【ctrl+c】進入中斷模式,此時,也需要切換到託管主執行緒。

1 0:002> ~0s
2 ntdll!NtDeviceIoControlFile+0x14:
3 00007fff`cc08d0c4 c3              ret
4 0:000>

                    繼續執行【!ClrStack -a】檢視託管執行緒呼叫棧。

 1 0:000> !ClrStack -a
 2 OS Thread Id: 0x1c98 (0)
 3         Child SP               IP Call Site
 4 0000001CC517E4B8 00007fffcc08d0c4 [InlinedCallFrame: 0000001cc517e4b8]
 5 0000001CC517E4B8 00007fffc0ff787a [InlinedCallFrame: 0000001cc517e4b8]
 6 0000001CC517E490 00007FFFC0FF787A Interop+Kernel32.ReadConsoleInput(IntPtr, INPUT_RECORD ByRef, Int32, Int32 ByRef)
 7     PARAMETERS:
 8         hConsoleInput = <no data>
 9         buffer = <no data>
10         numInputRecords_UseOne = <no data>
11         numEventsRead = <no data>
12     LOCALS:
13         <no data>
14         <no data>
15         <no data>
16         <no data>
17         <no data>
18         <no data>
19         <no data>
20 
21 0000001CC517E580 00007FFFC0FFAA0A System.ConsolePal.ReadKey(Boolean)
22     PARAMETERS:
23         intercept (<CLR reg>) = 0x0000000000000000
24     LOCALS:
25         <no data>
26         <no data>
27         <no data>
28         <no data>
29         <no data>
30         <no data>
31         <no data>
32         0x0000001CC517E5C0 = 0x000001363cc09cd8
33         <no data>
34         <no data>
35         <no data>
36         <no data>
37         <no data>
38 
39 0000001CC517E640 00007FFEB6431A22 ExampleCore_5_2.Program.Main(System.String[])
40     PARAMETERS:
41         args (0x0000001CC517E6C0) = 0x000001363cc08ea0
42     LOCALS:
43         0x0000001CC517E6A8 = 0x0000000000000000(n1 沒有根引用,已經被回收了)
44         0x0000001CC517E6A0 = 0x000001363cc09660(n2 有根引用,為被回收,但是它的代應該是提升到第 1 代了)

                    我們繼續使用【!eeheap -gc】命令檢視一下 GC 的詳情。

 1 0:000> !eeheap -gc
 2 Number of GC Heaps: 1
 3 generation 0 starts at 0x000001363C400028(第 0 代)
 4 generation 1 starts at 0x000001363CC00028(第 1 代)
 5 generation 2 starts at 0x00000176CEEC0008(第 2 代)
 6 ephemeral segment allocation context: none
 7          segment             begin         allocated         committed    allocated size    committed size
 8 generation 0:   (第 0 代起始地址)
 9 000001764E2BF1C0  000001363C400028  000001363C400028  000001363C411000  0x0(0)  0x10fd8(69592)
10 generation 1:   (第 1 代起始地址)
11 000001764E2BF320  000001363CC00028  000001363CC09CF0  000001363CC11000  0x9cc8(40136)  0x10fd8(69592)
12 generation 2:   (第 2 代起始地址)
13 000001363885E240  00000176CEEC0008  00000176CEEC0AF8  00000176CEED0000  0xaf0(2800)  0xfff8(65528)
14 000001764E2BF950  000001363F000028  000001363F000028  000001363F001000  0x0(0)  0xfd8(4056)
15 Large object heap starts at 0x0000000000000000
16          segment             begin         allocated         committed    allocated size    committed size
17 000001764E2BF3D0  000001363D000028  000001363D000028  000001363D001000  0x0(0)  0xfd8(4056)
18 Pinned object heap starts at 0x0000000000000000
19 000001764E2BEC40  000001363A400028  000001363A404018  000001363A411000  0x3ff0(16368)  0x10fd8(69592)
20 Total Allocated Size:              Size: 0xe7a8 (59304) bytes.
21 Total Committed Size:              Size: 0x33f58 (212824) bytes.
22 ------------------------------
23 GC Allocated Heap Size:    Size: 0xe7a8 (59304) bytes.
24 GC Committed Heap Size:    Size: 0x33f58 (212824) bytes.

                    我們已經執行了一次垃圾回收,代的起始地址也發生了變化。n1 地址變成了 0 ,表示被回收了,現在只有 n2 了,它的地址是:0x000001363cc09660,再次和每個代的起始地址比較吧,只是比較高位地址部分就可以了。n2 地址字首是:000001363cc,第 1 代起始地址的字首是:000001363CC,很明顯,是一致的,其他的就可以不用看了,當然,看看理解更好點。現在 n2 在第 1 代了。
                    我們繼續【g】,直到偵錯程式輸出“Press any key to Exit”字樣,效果如圖:
                    

                    我們繼續按組合鍵【ctrl+c】進入到偵錯程式的中斷模式,再次切換到託管執行緒上下文中。

1 0:003> ~0s
2 ntdll!NtDeviceIoControlFile+0x14:
3 00007fff`cc08d0c4 c3              ret

                    使用【!clrstack -a】命令,檢視一下託管執行緒的呼叫棧。

 1 0:000> !clrstack -a
 2 OS Thread Id: 0x1c98 (0)
 3         Child SP               IP Call Site
 4 0000001CC517E4B8 00007fffcc08d0c4 [InlinedCallFrame: 0000001cc517e4b8]
 5 0000001CC517E4B8 00007fffc0ff787a [InlinedCallFrame: 0000001cc517e4b8]
 6 0000001CC517E490 00007FFFC0FF787A Interop+Kernel32.ReadConsoleInput(IntPtr, INPUT_RECORD ByRef, Int32, Int32 ByRef)
 7     PARAMETERS:
 8         hConsoleInput = <no data>
 9         buffer = <no data>
10         numInputRecords_UseOne = <no data>
11         numEventsRead = <no data>
12     LOCALS:
13         <no data>
14         <no data>
15         <no data>
16         <no data>
17         <no data>
18         <no data>
19         <no data>
20 
21 0000001CC517E580 00007FFFC0FFAA0A System.ConsolePal.ReadKey(Boolean)
22     PARAMETERS:
23         intercept (<CLR reg>) = 0x0000000000000000
24     LOCALS:
25         <no data>
26         <no data>
27         <no data>
28         <no data>
29         <no data>
30         <no data>
31         <no data>
32         0x0000001CC517E5C0 = 0x000001363cc09cd8
33         <no data>
34         <no data>
35         <no data>
36         <no data>
37         <no data>
38 
39 0000001CC517E640 00007FFEB6431A45 ExampleCore_5_2.Program.Main(System.String[])
40     PARAMETERS:
41         args (0x0000001CC517E6C0) = 0x000001363cc08ea0
42     LOCALS:
43         0x0000001CC517E6A8 = 0x0000000000000000
44         0x0000001CC517E6A0 = 0x000001363cc09660

                    n2 物件在託管堆上的地址是:0x000001363cc09660,我們使用【!eeheap -gc】檢視 GC 的詳情。

 1 0:000> !eeheap -gc
 2 Number of GC Heaps: 1
 3 generation 0 starts at 0x000001363F000028(第 0 代 起始地址)
 4 generation 1 starts at 0x000001363C400028(第 1 代 起始地址)
 5 generation 2 starts at 0x00000176CEEC0008(第 2 代 起始地址)
 6 ephemeral segment allocation context: none
 7          segment             begin         allocated         committed    allocated size    committed size
 8 generation 0:(第 0 代)
 9 000001764E2BF950  000001363F000028  000001363F000028  000001363F011000  0x0(0)  0x10fd8(69592)
10 generation 1:(第 1 代)
11 000001764E2BF1C0  000001363C400028  000001363C400028  000001363C411000  0x0(0)  0x10fd8(69592)
12 generation 2:(第 2 代)
13 000001363885E240  00000176CEEC0008  00000176CEEC0AF8  00000176CEED0000  0xaf0(2800)  0xfff8(65528)
14 000001764E2BF320  000001363CC00028  000001363CC09CF0  000001363CC11000  0x9cc8(40136)  0x10fd8(69592)
15 Large object heap starts at 0x0000000000000000
16          segment             begin         allocated         committed    allocated size    committed size
17 000001764E2BF3D0  000001363D000028  000001363D000028  000001363D001000  0x0(0)  0xfd8(4056)
18 Pinned object heap starts at 0x0000000000000000
19 000001764E2BEC40  000001363A400028  000001363A404018  000001363A411000  0x3ff0(16368)  0x10fd8(69592)
20 Total Allocated Size:              Size: 0xe7a8 (59304) bytes.
21 Total Committed Size:              Size: 0x43f58 (278360) bytes.
22 ------------------------------
23 GC Allocated Heap Size:    Size: 0xe7a8 (59304) bytes.
24 GC Committed Heap Size:    Size: 0x43f58 (278360) bytes.

                    n2 物件的地址:0x000001363cc09660,我們在第 2 代裡,發現有2個記憶體段,第 2 個記憶體段的起始地址是:000001363CC00028,n2 物件的地址正好在第 2 代的第 2 個記憶體段的地址裡。

                2)、Windbg Preview 除錯
                    編譯專案,開啟【Windbg Preview】,依次點選【檔案】---【Launch executable】,載入我們的專案檔案:ExampleCore_5_2.exe,進入偵錯程式。
                    進入到偵錯程式,我們直接【g】執行偵錯程式,直到我們的控制檯程式輸出“Press any key to invoke GC”字樣。點選偵錯程式的【break】按鈕,中斷偵錯程式的執行。由於我們是手動中斷,需要切換到託管執行緒的呼叫棧上,執行命令【~0s】。

1 0:001> ~0s
2 ntdll!NtDeviceIoControlFile+0x14:
3 00007ff9`401cd0c4 c3              ret

                    繼續使用【!clrstack -a】命令,檢視託管執行緒棧的呼叫棧,列印出所有引數和區域性變數。

 1 0:000> !ClrStack -a
 2 OS Thread Id: 0x505c (0)
 3         Child SP               IP Call Site
 4 0000005C5277E668 00007ff9401cd0c4 [InlinedCallFrame: 0000005c5277e668] 
 5 0000005C5277E668 00007ff87a4d787a [InlinedCallFrame: 0000005c5277e668] 
 6 0000005C5277E640 00007ff87a4d787a Interop+Kernel32.ReadConsoleInput(IntPtr, INPUT_RECORD ByRef, Int32, Int32 ByRef) [/_/src/libraries/System.Console/src/Microsoft.Interop.LibraryImportGenerator/Microsoft.Interop.LibraryImportGenerator/LibraryImports.g.cs @ 470]
 7     PARAMETERS:
 8         hConsoleInput = <no data>
 9         buffer = <no data>
10         numInputRecords_UseOne = <no data>
11         numEventsRead = <no data>
12     LOCALS:
13         <no data>
14         <no data>
15         <no data>
16         <no data>
17         <no data>
18         <no data>
19         <no data>
20 
21 0000005C5277E730 00007ff87a4daa0a System.ConsolePal.ReadKey(Boolean) [/_/src/libraries/System.Console/src/System/ConsolePal.Windows.cs @ 334]
22     PARAMETERS:
23         intercept (<CLR reg>) = 0x0000000000000000
24     LOCALS:
25         <no data>
26         <no data>
27         <no data>
28         <no data>
29         <no data>
30         <no data>
31         <no data>
32         0x0000005C5277E770 = 0x000001eaf1009cd8
33         <no data>
34         <no data>
35         <no data>
36         <no data>
37         <no data>
38 
39 0000005C5277E7F0 00007ff7fcab19f9 ExampleCore_5_2.Program.Main(System.String[]) [E:\Visual Studio\...\ExampleCore_5_2\Program.cs @ 29]
40     PARAMETERS:(表示Main方法的引數,是一個陣列)
41         args (0x0000005C5277E870) = 0x000001eaf1008ea0
42     LOCALS:(表示是區域性變數)
43         0x0000005C5277E858 = 0x000001eaf1009640
44         0x0000005C5277E850 = 0x000001eaf1009660

                    紅色標註的兩項就是我們的區域性變數,分別是:n1 和 n2。接下來,我們就看看這兩個變數屬於哪一代。
                    繼續執行【!eeheap -gc】命令。

 1 0:000> !eeheap -gc
 2 
 3 ========================================
 4 Number of GC Heaps: 1
 5 ----------------------------------------
 6 Small object heap
 7          segment            begin        allocated        committed allocated size  committed size 
 8 generation 0:
 9     022b0269f320     01eaf1000028     01eaf100a630     01eaf1011000 0xa608 (42504)  0x11000 (69632)
10 generation 1:
11     022b0269f270     01eaf0c00028     01eaf0c00028     01eaf0c01000                 0x1000 (4096)  
12 generation 2:
13     022b0269f1c0     01eaf0800028     01eaf0800028     01eaf0801000                 0x1000 (4096)  
14 NonGC heap
15          segment            begin        allocated        committed allocated size  committed size 
16     01eaecb64230     022b832a0008     022b832a0ad8     022b832b0000 0xad0 (2768)    0x10000 (65536)
17 Large object heap
18          segment            begin        allocated        committed allocated size  committed size 
19     022b0269f3d0     01eaf1400028     01eaf1400028     01eaf1401000                 0x1000 (4096)  
20 Pinned object heap
21          segment            begin        allocated        committed allocated size  committed size 
22     022b0269ec40     01eaee800028     01eaee804018     01eaee811000 0x3ff0 (16368)  0x11000 (69632)
23 ------------------------------
24 GC Allocated Heap Size:    Size: 0xf0c8 (61640) bytes.
25 GC Committed Heap Size:    Size: 0x35000 (217088) bytes.

                    兩個區域性變數的地址分別是:0x000001eaf1009640 和 0x000001eaf1009660,第 0 代起始地址是 01eaf1000028,兩個區域性變數前8位都是一致的(看紅色部分),用這個地址和每個代的開始地址比較很容易知道屬於第 0 代。
                    我們繼續【g】執行偵錯程式,直到我們的控制檯程式輸出第二個“Press any key to invoke GC”字樣,點選偵錯程式【break】按鈕,中斷偵錯程式的執行,又因為是我們手動中斷,需要切換到託管執行緒上,執行命令【~0s】。

1 0:001> ~0s
2 ntdll!NtDeviceIoControlFile+0x14:
3 00007ff9`401cd0c4 c3              ret

                    我們再次執行【!clrstack -a】命令,檢視託管執行緒棧。

 1 0:000> !clrstack -a
 2 OS Thread Id: 0x505c (0)
 3         Child SP               IP Call Site
 4 0000005C5277E668 00007ff9401cd0c4 [InlinedCallFrame: 0000005c5277e668] 
 5 0000005C5277E668 00007ff87a4d787a [InlinedCallFrame: 0000005c5277e668] 
 6 0000005C5277E640 00007ff87a4d787a Interop+Kernel32.ReadConsoleInput(IntPtr, INPUT_RECORD ByRef, Int32, Int32 ByRef) [/_/src/libraries/System.Console/src/Microsoft.Interop.LibraryImportGenerator/Microsoft.Interop.LibraryImportGenerator/LibraryImports.g.cs @ 470]
 7     PARAMETERS:
 8         hConsoleInput = <no data>
 9         buffer = <no data>
10         numInputRecords_UseOne = <no data>
11         numEventsRead = <no data>
12     LOCALS:
13         <no data>
14         <no data>
15         <no data>
16         <no data>
17         <no data>
18         <no data>
19         <no data>
20 
21 0000005C5277E730 00007ff87a4daa0a System.ConsolePal.ReadKey(Boolean) [/_/src/libraries/System.Console/src/System/ConsolePal.Windows.cs @ 334]
22     PARAMETERS:
23         intercept (<CLR reg>) = 0x0000000000000000
24     LOCALS:
25         <no data>
26         <no data>
27         <no data>
28         <no data>
29         <no data>
30         <no data>
31         <no data>
32         0x0000005C5277E770 = 0x000001eaf1009cd8
33         <no data>
34         <no data>
35         <no data>
36         <no data>
37         <no data>
38 
39 0000005C5277E7F0 00007ff7fcab1a22 ExampleCore_5_2.Program.Main(System.String[]) [E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_5_2_1\Program.cs @ 35]
40     PARAMETERS:
41         args (0x0000005C5277E870) = 0x000001eaf1008ea0
42     LOCALS:
43         0x0000005C5277E858 = 0x0000000000000000(這裡變成0了,說明被回收了,也就是 n1 變數)
44         0x0000005C5277E850 = 0x000001eaf1009660

                    這裡的內容大部分都是相同的,紅色標註的注意看,一個值變成了 0 ,說明被(n1 = null;GC.Collect())回收了。0x000001eaf1009660 這個值就是 n2,由於執行了垃圾會後,n2 沒有被回收,肯定從第 0 代升級到第 1 代了,我們執行【!eeheap -gc】命令來驗證。

 1 0:000> !eeheap -gc
 2 
 3 ========================================
 4 Number of GC Heaps: 1
 5 ----------------------------------------
 6 Small object heap
 7          segment            begin        allocated        committed allocated size  committed size 
 8 generation 0:
 9     022b0269f1c0     01eaf0800028     01eaf0800028     01eaf0811000                 0x11000 (69632)
10 generation 1:
11     022b0269f320     01eaf1000028     01eaf1009cf0     01eaf1011000 0x9cc8 (40136)  0x11000 (69632)
12 generation 2:
13     022b0269f950     01eaf3400028     01eaf3400028     01eaf3401000                 0x1000 (4096)  
14 NonGC heap
15          segment            begin        allocated        committed allocated size  committed size 
16     01eaecb64230     022b832a0008     022b832a0af8     022b832b0000 0xaf0 (2800)    0x10000 (65536)
17 Large object heap
18          segment            begin        allocated        committed allocated size  committed size 
19     022b0269f3d0     01eaf1400028     01eaf1400028     01eaf1401000                 0x1000 (4096)  
20 Pinned object heap
21          segment            begin        allocated        committed allocated size  committed size 
22     022b0269ec40     01eaee800028     01eaee804018     01eaee811000 0x3ff0 (16368)  0x11000 (69632)
23 ------------------------------
24 GC Allocated Heap Size:    Size: 0xe7a8 (59304) bytes.
25 GC Committed Heap Size:    Size: 0x45000 (282624) bytes.

                  物件的地址是 0x000001eaf1009660,開始比較,第 0 代的開始地址:01eaf0800028,肯定不是,第 1 代的開始地址是:01eaf1000028,這個地址是符合的,也就是說 0x000001eaf1009660 這地址在 01eaf1000028 這地址裡面的,證明了我們的說法。
                  我們繼續【g】執行偵錯程式,直到我們的控制檯程式輸出“Press any key to Exit”字樣,點選偵錯程式的【break】按鈕,中斷偵錯程式的執行,我們還必須切換到託管執行緒上,執行命令【~0s】。

1 0:001> ~0s
2 ntdll!NtDeviceIoControlFile+0x14:
3 00007ff9`401cd0c4 c3              ret

                  再次執行【!clrstack -a】命令,檢視託管執行緒棧。

 1 0:000> !clrstack -a
 2 OS Thread Id: 0x505c (0)
 3         Child SP               IP Call Site
 4 0000005C5277E668 00007ff9401cd0c4 [InlinedCallFrame: 0000005c5277e668] 
 5 0000005C5277E668 00007ff87a4d787a [InlinedCallFrame: 0000005c5277e668] 
 6 0000005C5277E640 00007ff87a4d787a Interop+Kernel32.ReadConsoleInput(IntPtr, INPUT_RECORD ByRef, Int32, Int32 ByRef) [/_/src/libraries/System.Console/src/Microsoft.Interop.LibraryImportGenerator/Microsoft.Interop.LibraryImportGenerator/LibraryImports.g.cs @ 470]
 7     PARAMETERS:
 8         hConsoleInput = <no data>
 9         buffer = <no data>
10         numInputRecords_UseOne = <no data>
11         numEventsRead = <no data>
12     LOCALS:
13         <no data>
14         <no data>
15         <no data>
16         <no data>
17         <no data>
18         <no data>
19         <no data>
20 
21 0000005C5277E730 00007ff87a4daa0a System.ConsolePal.ReadKey(Boolean) [/_/src/libraries/System.Console/src/System/ConsolePal.Windows.cs @ 334]
22     PARAMETERS:
23         intercept (<CLR reg>) = 0x0000000000000000
24     LOCALS:
25         <no data>
26         <no data>
27         <no data>
28         <no data>
29         <no data>
30         <no data>
31         <no data>
32         0x0000005C5277E770 = 0x000001eaf1009cd8
33         <no data>
34         <no data>
35         <no data>
36         <no data>
37         <no data>
38 
39 0000005C5277E7F0 00007ff7fcab1a45 ExampleCore_5_2.Program.Main(System.String[]) [E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_5_2\Program.cs @ 40]
40     PARAMETERS:
41         args (0x0000005C5277E870) = 0x000001eaf1008ea0
42     LOCALS:
43         0x0000005C5277E858 = 0x0000000000000000
44         0x0000005C5277E850 = 0x000001eaf1009660(這就是 n2 區域性變數)

                  有了物件的地址,我們繼續執行【!eeheap -gc】命令檢視 GC 的詳情。

 1 0:000> !eeheap -gc
 2 
 3 ========================================
 4 Number of GC Heaps: 1
 5 ----------------------------------------
 6 Small object heap
 7          segment            begin        allocated        committed allocated size  committed size 
 8 generation 0:
 9     022b0269f950     01eaf3400028     01eaf3400028     01eaf3411000                 0x11000 (69632)
10 generation 1:
11     022b0269f1c0     01eaf0800028     01eaf0800028     01eaf0811000                 0x11000 (69632)
12 generation 2:
13     022b0269f320     01eaf1000028     01eaf1009cf0     01eaf1011000 0x9cc8 (40136)  0x11000 (69632)
14 NonGC heap
15          segment            begin        allocated        committed allocated size  committed size 
16     01eaecb64230     022b832a0008     022b832a0af8     022b832b0000 0xaf0 (2800)    0x10000 (65536)
17 Large object heap
18          segment            begin        allocated        committed allocated size  committed size 
19     022b0269f3d0     01eaf1400028     01eaf1400028     01eaf1401000                 0x1000 (4096)  
20 Pinned object heap
21          segment            begin        allocated        committed allocated size  committed size 
22     022b0269ec40     01eaee800028     01eaee804018     01eaee811000 0x3ff0 (16368)  0x11000 (69632)
23 ------------------------------
24 GC Allocated Heap Size:    Size: 0xe7a8 (59304) bytes.
25 GC Committed Heap Size:    Size: 0x55000 (348160) bytes.

                  物件的地址是:0x000001eaf1009660,第 0 代的開始地址是:01eaf3400028,比較前6-8位即可,不符合,第 1 代開始地址是:01eaf0800028,也不符合,第 2 代的開始地址:01eaf1000028,我們看到了,地址前8位是一樣的,所以也就證明了我們的說法,n2 已經提升到第 2 代了。


        3.2.2、根物件
            A、基礎知識
                C# 的引用跟蹤回收演算法,核心在於尋找【根物件】,凡是託管堆上的某個物件被【根物件】所引用,GC就不會回收這個物件的。
                GC 本身並不會監測哪些物件仍然被引用,而是使用 CLR 中其他了解物件生命週期的元件。
                通常3個地方有根物件。
                I、執行緒棧
                    方法作用域下的引用型別,自然就是根物件。
                II、終結器佇列(Finalizer queues)
                    帶有解構函式的物件自然會被加入到【終結器佇列】中,終結執行緒會在物件成為垃圾物件後的某個時刻執行物件的解構函式。
                III、控制代碼表(handle table)
                    CLR 為每個應用程式域提供一組控制代碼表,在這些控制代碼表中包含了指向託管堆上固定引用型別的指標。換句話說,凡是被 Strong、Pinned 標記的物件都會被放入到【控制代碼表】中,比如:static 物件。控制代碼表就是在 CLR 私有堆中具有一個字典型別的資料結構,用於儲存被 Strong、Pinned 標記的物件。
                    控制代碼型別是一種值型別,如果想轉儲出控制代碼的內容,只能使用【!DumpVC】,【!DumpObj】命令是針對引用型別的。
                IIII、即時編譯器JIT
                    它負責將 IL 程式碼轉換為機器碼,因此它知道在任意時刻有哪些區域性變數仍然被認為是活躍的。JIT 編譯器將這些資訊維護在一張表中,當 GC 要查詢活躍物件的時候,會用到這張表。

            B、眼見為實
                除錯原始碼:ExampleCore_5_3
                除錯任務:使用【!gcroot】命令查詢物件的根引用。
                1)、NTSD 除錯
                    編譯專案,開啟【Visual Studio 2022 Developer Command Prompt v17.9.6】命令列工具,輸入命令【NTSD E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_5_3\bin\Debug\net8.0\ExampleCore_5_3.exe】,開啟【NTSD】偵錯程式。
                    【g】直接執行偵錯程式,直到偵錯程式輸出“Press any key to Exit”字樣,效果如圖:
                    

                    按組合鍵【ctrl+c】進入中斷模式,然後切換到託管執行緒上下文。

1 0:001> ~0s
2 ntdll!NtDeviceIoControlFile+0x14:
3 00007ff8`bec2d0c4 c3              ret

                    繼續使用【!clrstack -a】命令檢視一下託管執行緒呼叫棧的情況。

 1 0:000> !clrstack -a
 2 OS Thread Id: 0x3a00 (0)
 3         Child SP               IP Call Site
 4 00000030723FE7A8 00007ff8bec2d0c4 [InlinedCallFrame: 00000030723fe7a8]
 5 00000030723FE7A8 00007ff8b261787a [InlinedCallFrame: 00000030723fe7a8]
 6 00000030723FE780 00007FF8B261787A Interop+Kernel32.ReadConsoleInput(IntPtr, INPUT_RECORD ByRef, Int32, Int32 ByRef)
 7     PARAMETERS:
 8         hConsoleInput = <no data>
 9         buffer = <no data>
10         numInputRecords_UseOne = <no data>
11         numEventsRead = <no data>
12     LOCALS:
13         <no data>
14         <no data>
15         <no data>
16         <no data>
17         <no data>
18         <no data>
19         <no data>
20 
21 00000030723FE870 00007FF8B261AA0A System.ConsolePal.ReadKey(Boolean)
22     PARAMETERS:
23         intercept (<CLR reg>) = 0x0000000000000000
24     LOCALS:
25         <no data>
26         <no data>
27         <no data>
28         <no data>
29         <no data>
30         <no data>
31         <no data>
32         0x00000030723FE8B0 = 0x0000014c5f809868
33         <no data>
34         <no data>
35         <no data>
36         <no data>
37         <no data>
38 
39 00000030723FE930 00007FF7AD521AF2 ExampleCore_5_3.Program.Run()
40     PARAMETERS:
41         this (0x00000030723FE990) = 0x0000014c5f809640
42     LOCALS:
43         0x00000030723FE978 = 0x0000014c5f809660(Name 型別例項的地址)
44 
45 00000030723FE990 00007FF7AD521988 ExampleCore_5_3.Program.Main(System.String[])
46     PARAMETERS:
47         args (0x00000030723FE9D0) = 0x0000014c5f808ea0
48     LOCALS:
49         0x00000030723FE9B8 = 0x0000014c5f809640

                    我們可以使用【!do 0x0000014c5f809660】命令或者【!DumpObj 0x0000014c5f809660】命令驗證一下是否正確。

 1 0:000> !do 0x0000014c5f809660
 2 Name:        ExampleCore_5_3.Name
 3 MethodTable: 00007ff7ad5d94b0
 4 EEClass:     00007ff7ad5e2068
 5 Tracked Type: false
 6 Size:        32(0x20) bytes
 7 File:        E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_5_3\bin\Debug\net8.0\ExampleCore_5_3.dll
 8 Fields:
 9               MT    Field   Offset                 Type VT     Attr            Value Name
10 00007ff7ad50ec08  4000001        8        System.String  0 instance 0000018cf1b004e0 _first
11 00007ff7ad50ec08  4000002       10        System.String  0 instance 0000018cf1b00500 _last

                    我們有了物件的地址,就可以使用【!gcroot 0x0000014c5f809660】命令檢視一下它的根引用。

 1 0:000> !gcroot 0x0000014c5f809660
 2 Thread 3a00:(OSID 3a00,是主執行緒)
 3     00000030723FE930 00007FF7AD521AF2 ExampleCore_5_3.Program.Run() [E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_5_3\Program.cs @ 47]
 4         rbp-8: 00000030723fe978
 5             ->  0000014C5F809660 ExampleCore_5_3.Name
 6 
 7 Thread 3170:(OSID 3170,是透過 Thread 啟動的執行緒)
 8     00000030731FF960 00007FF7AD521CCD ExampleCore_5_3.Program.Worker(System.Object) [E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_5_3\Program.cs @ 59]
 9         rbp-8: 00000030731ff9a8
10             ->  0000014C5F809660 ExampleCore_5_3.Name
11 
12     00000030731FF960 00007FF7AD521CCD ExampleCore_5_3.Program.Worker(System.Object) [E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_5_3\Program.cs @ 59]
13         rbp+18: 00000030731ff9c8
14             ->  0000014C5F809660 ExampleCore_5_3.Name
15 
16 HandleTable:(控制代碼表)
17     0000014C5B4113E8 (strong handle)
18     -> 0000014C5D000028 System.Object[]
19     -> 0000014C5F809660 ExampleCore_5_3.Name
20 
21 Found 4 unique roots (run '!gcroot -all' to see all roots).

                     我們看到了 ExampleCore_5_3.Name 型別在 2個執行緒和一個控制代碼表中有引用。我們可以使用【!t】命令或者【!threads】命令檢視託管所有執行緒。

 1 0:000> !threads
 2 ThreadCount:      3
 3 UnstartedThread:  0
 4 BackgroundThread: 1
 5 PendingThread:    0
 6 DeadThread:       0
 7 Hosted Runtime:   no
 8                                                                                                             Lock
 9  DBG   ID     OSID ThreadOBJ           State GC Mode     GC Alloc Context                  Domain           Count Apt Exception
10    0    1     3a00 0000014C5B4C6430    2a020 Preemptive  0000014C5F8098B0:0000014C5F80A618 0000014C5B4BBF70 -00001 MTA
11    6    2     35e0 0000014C5B56BC40    21220 Preemptive  0000000000000000:0000000000000000 0000014C5B4BBF70 -00001 Ukn (Finalizer)
12    8    4     3170 0000014C5B4D1E80  202b020 Preemptive  0000014C5F80AC88:0000014C5F80C638 0000014C5B4BBF70 -00001 MTA

                    OSID 是 3a00 的執行緒是主執行緒,也就是我們當前操作的執行緒。我們可以去 OSID 是 3170 的執行緒去看看,首先,切換執行緒,執行命令【~~[3170]s】。
                    繼續執行【!clrstack -a】命令,檢視當前的託管執行緒呼叫棧。

 1 0:008> !clrstack -a
 2 OS Thread Id: 0x3170 (8)
 3         Child SP               IP Call Site
 4 00000030731FF838 00007ff8bec2d664 [HelperMethodFrame: 00000030731ff838] System.Threading.Thread.SleepInternal(Int32)
 5 00000030731FF930 00007FF7AD521E21 System.Threading.Thread.Sleep(Int32)
 6     PARAMETERS:
 7         millisecondsTimeout = <no data>
 8 
 9 00000030731FF960 00007FF7AD521CCD ExampleCore_5_3.Program.Worker(System.Object)
10     PARAMETERS:
11         this (0x00000030731FF9C0) = 0x0000014c5f809640
12         o (0x00000030731FF9C8) = 0x0000014c5f809660(這是傳入的引數,也就是 Worker 方法的 o 引數。)
13     LOCALS:
14         0x00000030731FF9A8 = 0x0000014c5f809660(這裡就是 n1 區域性變數的值,程式碼就是:var n1 = (Name)o!)
15         0x00000030731FF9A4 = 0x0000000000000000
16         0x00000030731FF9A0 = 0x0000000000000001
17 
18 00000030731FFC10 00007ff80d08b8d3 [DebuggerU2MCatchHandlerFrame: 00000030731ffc10] 

                    我們看到在 ExampleCore_5_3.Program.Worker 方法裡有引用了 ExampleCore_5_3.Name 型別。這就找到了 ExampleCore_5_3.Name 型別的所有根引用。


                2)、Windbg Preview 除錯
                    編譯專案,開啟【Windbg Preview】偵錯程式,依次點選【檔案】---【Launch executable】,載入我們的專案檔案:ExampleCore_5_3.exe,進入到偵錯程式。
                    我們進入偵錯程式後,直接【g】執行偵錯程式,直到我們的控制檯程式輸出“Press any key to Exit”字樣。效果如圖:
                    

                    我們在偵錯程式上點選【break】按鈕,進入中斷模式。
                    由於手動中斷,我們必須切換到託管執行緒上下文,因為目前是在偵錯程式的執行緒上下文中。

1 0:001> ~0s
2 ntdll!NtDeviceIoControlFile+0x14:
3 00007fff`cc08d0c4 c3              ret

                    我們必須先找到我們目標物件的地址,所以先使用【!clrstack -a】檢視一下託管執行緒呼叫棧。

 1 0:000> !ClrStack -a
 2 OS Thread Id: 0x1fa0 (0)
 3         Child SP               IP Call Site
 4 000000BEF817E6F8 00007fffcc08d0c4 [InlinedCallFrame: 000000bef817e6f8] 
 5 000000BEF817E6F8 00007fff1a4d787a [InlinedCallFrame: 000000bef817e6f8] 
 6 000000BEF817E6D0 00007fff1a4d787a Interop+Kernel32.ReadConsoleInput(IntPtr, INPUT_RECORD ByRef, Int32, Int32 ByRef) [/_/src/libraries/System.Console/src/Microsoft.Interop.LibraryImportGenerator/Microsoft.Interop.LibraryImportGenerator/LibraryImports.g.cs @ 470]
 7     PARAMETERS:
 8         hConsoleInput = <no data>
 9         buffer = <no data>
10         numInputRecords_UseOne = <no data>
11         numEventsRead = <no data>
12     LOCALS:
13         <no data>
14         <no data>
15         <no data>
16         <no data>
17         <no data>
18         <no data>
19         <no data>
20 
21 000000BEF817E7C0 00007fff1a4daa0a System.ConsolePal.ReadKey(Boolean) [/_/src/libraries/System.Console/src/System/ConsolePal.Windows.cs @ 334]
22     PARAMETERS:
23         intercept (<CLR reg>) = 0x0000000000000000
24     LOCALS:
25         <no data>
26         <no data>
27         <no data>
28         <no data>
29         <no data>
30         <no data>
31         <no data>
32         0x000000BEF817E800 = 0x000001e689c09868
33         <no data>
34         <no data>
35         <no data>
36         <no data>
37         <no data>
38 
39 000000BEF817E880 00007ffea92f1af2 ExampleCore_5_3.Program.Run() [E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_5_3\Program.cs @ 47]
40     PARAMETERS:
41         this (0x000000BEF817E8E0) = 0x000001e689c09640
42     LOCALS:
43         0x000000BEF817E8C8 = 0x000001e689c09660(這就是 Name 型別在託管堆中的地址)
44 
45 000000BEF817E8E0 00007ffea92f1988 ExampleCore_5_3.Program.Main(System.String[]) [E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_5_3\Program.cs @ 33]
46     PARAMETERS:
47         args (0x000000BEF817E920) = 0x000001e689c08ea0
48     LOCALS:
49         0x000000BEF817E908 = 0x000001e689c09640

                    0x000001e689c09660 地址有了,我們還是要確定一下是不是 Name 型別的,可以使用【!DumpObj 0x000001e689c09660】命令。

 1 0:000> !DumpObj 0x000001e689c09660
 2 Name:        ExampleCore_5_3.Name
 3 MethodTable: 00007ffea93a94b0
 4 EEClass:     00007ffea93b2068
 5 Tracked Type: false
 6 Size:        32(0x20) bytes
 7 File:        E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_5_3\bin\Debug\net8.0\ExampleCore_5_3.dll
 8 Fields:
 9               MT    Field   Offset                 Type VT     Attr            Value Name
10 00007ffea92dec08  4000001        8        System.String  0 instance 000002271bfd04e0 _first
11 00007ffea92dec08  4000002       10        System.String  0 instance 000002271bfd0500 _last

                    證明我們的說法,既然我們得到了物件的地址,我們就可以針對該地址使用【!gcroot 000001e689c09660】命令,檢視一下它的根引用。

 1 0:000> !gcroot 0x0000017750409660
 2 Caching GC roots, this may take a while.
 3 Subsequent runs of this command will be faster.
 4 
 5 HandleTable:(這裡是控制代碼表)
 6     000001774c0813e8 (strong handle)
 7           -> 01774dc00028     System.Object[] 
 8           -> 017750409660     ExampleCore_5_3.Name 
 9 
10 Thread 574:(這是一個執行緒,OSID 是 574,該執行緒是主執行緒)
11     52e5f7e630 7ff7a99b1af2 ExampleCore_5_3.Program.Run() [E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_5_3\Program.cs @ 47]
12         rbp-8: 00000052e5f7e678
13           -> 017750409660     ExampleCore_5_3.Name 
14 
15 Thread 36e8:(這是一個執行緒,OSID 是 36e8,該執行緒是透過 New Thread 建立的)
16     52e6b7f480 7ff7a99b1ccd ExampleCore_5_3.Program.Worker(System.Object) [E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_5_3\Program.cs @ 59]
17         rbp-8: 00000052e6b7f4c8
18           -> 017750409660     ExampleCore_5_3.Name 
19 
20         rbp+18: 00000052e6b7f4e8
21           -> 017750409660     ExampleCore_5_3.Name 
22 
23 Found 4 unique roots.

                    我們可以使用【!t】或者【!threads】命令,列出所有的託管執行緒來檢視上面的出現的兩個執行緒。

 1 0:000> !t
 2 ThreadCount:      3
 3 UnstartedThread:  0
 4 BackgroundThread: 1
 5 PendingThread:    0
 6 DeadThread:       0
 7 Hosted Runtime:   no
 8                                                                                                             Lock  
 9  DBG   ID     OSID ThreadOBJ           State GC Mode     GC Alloc Context                  Domain           Count Apt Exception
10    0    1      574 000001774C11D510    2a020 Preemptive  00000177504098B0:000001775040A618 000001774c112fa0 -00001 MTA 
11    6    2     3a3c 000001774C1C2C10    21220 Preemptive  0000000000000000:0000000000000000 000001774c112fa0 -00001 Ukn (Finalizer) 
12    8    4     36e8 000001774C1295F0  202b020 Preemptive  000001775040AC88:000001775040C638 000001774c112fa0 -00001 MTA 

                    在這裡說明一下,這裡【!gcroot】命令的輸出內容和原著書上輸出的內容是由很大區別的,平臺不一樣了。

                    我們在使用【!gcroot】命令輸出的結果中,有兩個執行緒的輸出,我們可以切換到執行緒上檢視詳情。
                    首先我們先切換到 OSID 為 574 的執行緒上,執行命令【~~[574]s】,檢視一下具體情況。

1 0:000> ~~[574]s
2 ntdll!NtDeviceIoControlFile+0x14:
3 00007ff8`bec2d0c4 c3              ret

                    其實OSID 是 574 的執行緒就是主執行緒,【!clrstack -a】命令和【!gcroot】命令輸出結果和前面是一樣的。我們在切換到 OSID 是 36e8 的執行緒上看看具體情況。

1 0:000> ~~[36e8]s
2 ntdll!NtDelayExecution+0x14:
3 00007ff8`bec2d664 c3              ret

                    繼續使用【!clrstack -a】命令檢視託管執行緒呼叫棧的情況。

 1 0:008> !clrstack -a
 2 OS Thread Id: 0x36e8 (8)
 3         Child SP               IP Call Site
 4 00000052E6B7F358 00007ff8bec2d664 [HelperMethodFrame: 00000052e6b7f358] System.Threading.Thread.SleepInternal(Int32)
 5 00000052E6B7F450 00007ff8083fe5a1 System.Threading.Thread.Sleep(Int32) [/_/src/libraries/System.Private.CoreLib/src/System/Threading/Thread.cs @ 367]
 6     PARAMETERS:
 7         millisecondsTimeout = <no data>
 8 
 9 00000052E6B7F480 00007ff7a99b1ccd ExampleCore_5_3.Program.Worker(System.Object) [E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_5_3\Program.cs @ 59]
10     PARAMETERS:
11         this (0x00000052E6B7F4E0) = 0x0000017750409640
12         o (0x00000052E6B7F4E8) = 0x0000017750409660
13     LOCALS:
14         0x00000052E6B7F4C8 = 0x0000017750409660
15         0x00000052E6B7F4C4 = 0x0000000000000000
16         0x00000052E6B7F4C0 = 0x0000000000000001
17 
18 00000052E6B7F730 00007ff80950b8d3 [DebuggerU2MCatchHandlerFrame: 00000052e6b7f730] 

                    我們看到了在 ExampleCore_5_3.Program.Worker 方法的呼叫棧中有一個區域性變數的地址是有點眼熟的,0x0000017750409660,這個地址就是我們 Name 型別在託管堆上的地址。說明,在編號為 0x36e8 這個執行緒裡也引用了我們的 Name 型別。

                    如果大家不信,可以使用【!do 0x0000017750409660】命令或者【!DumpObj 0x0000017750409660】命令證明一下。

 1 0:008> !do 0x0000017750409660
 2 Name:        ExampleCore_5_3.Name
 3 MethodTable: 00007ff7a9a694b0
 4 EEClass:     00007ff7a9a72068
 5 Tracked Type: false
 6 Size:        32(0x20) bytes
 7 File:        E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_5_3\bin\Debug\net8.0\ExampleCore_5_3.dll
 8 Fields:
 9               MT    Field   Offset                 Type VT     Attr            Value Name
10 00007ff7a999ec08  4000001        8        System.String  0 instance 000001b7e28104e0 _first
11 00007ff7a999ec08  4000002       10        System.String  0 instance 000001b7e2810500 _last


        3.2.3、終結操作
            A、基礎知識
                當我們宣告的型別使用一些非託管資源的時候,例如:檔案控制代碼、資料庫連線、互斥體等,就需要做特別的處理,GC 才能安全、可靠的回收該物件所佔的資源。如果處理不當,雖然託管物件所佔用的記憶體被回收了,但是物件所使用的非託管資源卻不會被回收,因為 GC 並不知道這些非託管資源的存在。

                為了提供合適的回收策略,CLR 引入了終結器的概念,當物件被回收時,它的終結器就會被執行。當這個類被編譯為 IL 程式碼時,終結方法會被編譯一個名為 Finalize 的函式。由於,垃圾收集器實際上是一個自動記憶體管理器,因此,在垃圾收集過程中,它需要執行終結程式碼。

                當一個型別包含了終結器時,GC 的處理也會有所不同。為了記錄哪些物件擁有終結器,GC 維護了一個終結佇列(Finalization Queue)。如果在託管堆上建立的物件中包含終結器,那麼在建立過程中將被自動放入終結佇列中。需要注意,終結佇列並沒有包含那些被認為是垃圾的物件,而是包含了所有帶有終結器並在託管堆上處於活躍狀態的物件

                如果某個帶有終結器的物件不存在任何根引用了,並且啟動了垃圾回收的過程,那麼 GC 會把這個物件放入到另外一個佇列中,即 F-Reachable 佇列(終結可達佇列)。這個佇列包含了所有帶有終結器並且被作為垃圾的物件,這些物件的終結器都將被執行。在 F-Reachable 佇列上的所有物件都被視為仍然存在根引用。需要注意的是:在垃圾收集過程中,並不會執行 F-Reachable 佇列中每個物件的終結器的程式碼,這些程式碼將在一個特殊的執行緒中執行,它就是每個 .NET 程序的終結執行緒(Finalization Thread)。在收到 GC 的請求時,終結執行緒會啟動並且檢視 F-Reachable 佇列的狀態。如果在 F-Reachable 佇列上有任何的物件存在,那麼它會依次執行這些物件的終結方法。

                在垃圾收集過程結束後,帶有終結器的物件會出現在 F-Reachable 佇列中(根物件引用存在且是活躍的),直到終結執行緒執行他們的 Finalize 方法。此時,物件將從 F-Reachable 佇列中移走,並且這些物件也被認為不存在根物件引用了,從而真正的被垃圾收集器回收了。

                咱們來一張圖舉例說明一下,就很容易理解了。
                

                在上圖的 步驟1 中分配物件D 和物件E,它們各自帶有一個 Finalize 方法。在分配過程中,這些物件除了被放在託管堆上,還被放在終結佇列中,表示這些物件不被使用時需要執行終結操作。在 步驟2 中,當垃圾收集過程啟動時,物件D 和物件E 都不存在根物件引用。此時,這兩個物件將從終結佇列中移動到 F-Reachable 佇列中,表示可以執行它們的 Finalize 方法了。在接下來的某個時刻,步驟3 會被執行,終結執行緒也會啟動,並開始執行這兩個物件的 Finalize 方法。即使在終結器執行完成後,這兩個物件仍然存在於 F-Reachable 佇列中。最後在 步驟4 中再次啟動了垃圾回收過程,這些物件會被移出 F-Reachable 佇列(不再有根物件引用),然後又垃圾收集器從託管堆上回收。

                需要注意的是,雖然有一個專門的執行緒執行 Finalize 方法,但是 CLR 並不能保證這些執行緒將在何時啟動執行。由於在物件中包含了一些資源和在等待資源被回收時需要的時間過長,微軟又提出了一種明確的清楚模式,例如:IDisposable 模式或者 Close 模式。當使用終結型別時,背後要做大量的事情,CLR 不僅需要額外的資料結構(終結佇列和 F-Reachable 佇列),還需要一個專門的執行緒執行物件的 Finalize 方法。具有終結器的型別,無法僅透過一次垃圾回收操作就被回收,而是需要兩次。這些物件會被提升到第 1 代,從而使它成為一種開銷較高的物件。

            B、眼見為實
                除錯原始碼:ExampleCore_5_4
                除錯任務:透過偵錯程式觀察帶有終結器的物件是如何被回收的。
                1)、NTSD 除錯
                    編譯專案,開啟【Visual Studio 2022 Developer Command Prompt v17.9.6】命令列工具,輸入命令【NTSD E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_5_4\bin\Debug\net8.0\ExampleCore_5_4.exe】開啟偵錯程式。
                    進入偵錯程式後,直接【g】執行程式,直到偵錯程式輸出“Press any key to GC1!”字樣。
                    

                    此時,我們按【ctrl+c】組合鍵,中斷偵錯程式的執行,開始我們的除錯。
                    直接執行【!FinalizeQueue】命令,檢視終結佇列的詳情。

 1 0:002> !FinalizeQueue
 2 SyncBlocks to be cleaned up: 0
 3 Free-Threaded Interfaces to be released: 0
 4 MTA Interfaces to be released: 0
 5 STA Interfaces to be released: 0
 6 ----------------------------------
 7 generation 0 has 0 finalizable objects (0000013587E3E0E0->0000013587E3E0E0)
 8 generation 1 has 0 finalizable objects (0000013587E3E0E0->0000013587E3E0E0)
 9 generation 2 has 0 finalizable objects (0000013587E3E0E0->0000013587E3E0E0)
10 Ready for finalization 13 objects (0000013587E3E0E0->0000013587E3E148)
11 Statistics for all finalizable objects (including all objects ready for finalization):
12               MT    Count    TotalSize Class Name
13 00007ffeab9694a8        1           24 ExampleCore_5_4_1.NativeEvent
14 00007ffeab9656f8        2           48 System.WeakReference`1[[System.Diagnostics.Tracing.EventSource, System.Private.CoreLib]]
15 00007ffeab963d00        4           96 System.WeakReference`1[[System.Diagnostics.Tracing.EventProvider, System.Private.CoreLib]]
16 00007ffeab9685d0        1          184 System.Diagnostics.Tracing.NativeRuntimeEventSource
17 00007ffeab962b28        4          256 System.Diagnostics.Tracing.EventSource+OverrideEventProvider
18 00007ffeab962208        1          400 System.Diagnostics.Tracing.RuntimeEventSource
19 Total 13 objects

                    此時,我們可以看到第 0 代、第 1 代和第 2 代都沒有任何可終結的物件。因為我們還沒有執行第一次的【垃圾回收】,但是此時【Ready for finalization 13 objects】說明已經有13個物件可以執行【終結】方法的操作了,這一點和原著是有區別的。

                    我們可以使用【dp 0000013587E3E0E0】命令,檢視改地址的儲存的資料,其實就是13個要執行 Finalize 方法的物件。

1 0:002> dp 0000013587E3E0E0
2 00000135`87e3e0e0  00000135`8c408fd0 00000135`8c409200
3 00000135`87e3e0f0  00000135`8c409298 00000135`8c4092b0
4 00000135`87e3e100  00000135`8c409330 00000135`8c409390
5 00000135`87e3e110  00000135`8c4094c8 00000135`8c409560
6 00000135`87e3e120  00000135`8c409578 00000135`8c4095f8
7 00000135`87e3e130  00000135`8c409610 00000135`8c409628
8 00000135`87e3e140  00000135`8c409658 baadf00d`baadf00d
9 00000135`87e3e150  baadf00d`baadf00d baadf00d`baadf00d

                    00000135`8c409658 這地址應該就是我們定義的 ExampleCore_5_4_1.NativeEvent 型別。棧地址是有由高到低的分配,我們最早宣告 ExampleCore_5_4_1.NativeEvent 型別,它的地址肯定是最高的。執行【!do 00000135`8c409658】命令驗證一下。

0:002> !do 00000135`8c409658
Name:        ExampleCore_5_4.NativeEvent
MethodTable: 00007ffeab9694a8
EEClass:     00007ffeab971ff0
Tracked Type: false
Size:        24(0x18) bytes
File:        E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_5_4\bin\Debug\net8.0\ExampleCore_5_4.dll
Fields:
              MT    Field   Offset                 Type VT     Attr            Value Name
00007ffeab8970a0  4000001        8        System.IntPtr  1 instance 000000000000028C _nativeHandle

                    接下來,我們【g】繼續恢復偵錯程式的執行,完成第一次【垃圾回收】。效果如圖:
                    

                    此時,我們在【ctrl+c】組合鍵進入中斷模式,繼續執行【!FinalizeQueue】檢視終結佇列的情況。

 1 0:002> !FinalizeQueue
 2 SyncBlocks to be cleaned up: 0
 3 Free-Threaded Interfaces to be released: 0
 4 MTA Interfaces to be released: 0
 5 STA Interfaces to be released: 0
 6 ----------------------------------
 7 generation 0 has 13 finalizable objects (0000013587E3E0E0->0000013587E3E148)(執行第一次垃圾回收,第 0 代有 13 個要終結的物件)
 8 generation 1 has 0 finalizable objects (0000013587E3E0E0->0000013587E3E0E0)
 9 generation 2 has 0 finalizable objects (0000013587E3E0E0->0000013587E3E0E0)
10 Ready for finalization 0 objects (0000013587E3E148->0000013587E3E148)
11 Statistics for all finalizable objects (including all objects ready for finalization):
12               MT    Count    TotalSize Class Name
13 00007ffeab9694a8        1           24 ExampleCore_5_4_1.NativeEvent
14 00007ffeab9656f8        2           48 System.WeakReference`1[[System.Diagnostics.Tracing.EventSource, System.Private.CoreLib]]
15 00007ffeab963d00        4           96 System.WeakReference`1[[System.Diagnostics.Tracing.EventProvider, System.Private.CoreLib]]
16 00007ffeab9685d0        1          184 System.Diagnostics.Tracing.NativeRuntimeEventSource
17 00007ffeab962b28        4          256 System.Diagnostics.Tracing.EventSource+OverrideEventProvider
18 00007ffeab962208        1          400 System.Diagnostics.Tracing.RuntimeEventSource
19 Total 13 objects

                    此時,已經經歷了第一次的【垃圾回收】,在第 0 代有 13 個可以終結的物件。
                    我們【g】繼續恢復偵錯程式的執行,直到偵錯程式輸出“Press any key to Exit!”字樣。效果如圖:
                    

                    此時,已經已經完成第二次的【垃圾回收】了,按【ctrl+c】組合鍵進入中斷模式,我們繼續執行【!FinalizeQueue】命令。

 1 0:002> !FinalizeQueue
 2 SyncBlocks to be cleaned up: 0
 3 Free-Threaded Interfaces to be released: 0
 4 MTA Interfaces to be released: 0
 5 STA Interfaces to be released: 0
 6 ----------------------------------
 7 generation 0 has 0 finalizable objects (0000023E93A0D178->0000023E93A0D178)(第 0 代沒有了)
 8 generation 1 has 13 finalizable objects (0000023E93A0D110->0000023E93A0D178)(第 1 代有了 13 個可以終結的物件,物件生命週期提升了)
 9 generation 2 has 0 finalizable objects (0000023E93A0D110->0000023E93A0D110)
10 Ready for finalization 0 objects (0000023E93A0D178->0000023E93A0D178)
11 Statistics for all finalizable objects (including all objects ready for finalization):
12               MT    Count    TotalSize Class Name
13 00007ffeab0d94a8        1           24 ExampleCore_5_4.NativeEvent
14 00007ffeab0d56f8        2           48 System.WeakReference`1[[System.Diagnostics.Tracing.EventSource, System.Private.CoreLib]]
15 00007ffeab0d3d00        4           96 System.WeakReference`1[[System.Diagnostics.Tracing.EventProvider, System.Private.CoreLib]]
16 00007ffeab0d85d0        1          184 System.Diagnostics.Tracing.NativeRuntimeEventSource
17 00007ffeab0d2b28        4          256 System.Diagnostics.Tracing.EventSource+OverrideEventProvider
18 00007ffeab0d2208        1          400 System.Diagnostics.Tracing.RuntimeEventSource
19 Total 13 objects

                    我們看到了結果,物件的生命週期提升了,從第 0 代提升到第 1 代了,也就說明了,區域性終結器方法的物件一次是沒辦法得到回收的,生命週期變長了。
                    我們可以看看所有執行緒的呼叫棧,執行命令【~*kn】。

  1 0:009> ~*kn
  2 
  3    0  Id: 2200.2154 Suspend: 1 Teb: 00000064`c56e3000 Unfrozen
  4  # Child-SP          RetAddr               Call Site
  5 00 00000064`c597e068 00007fff`dbae7861     ntdll!NtDeviceIoControlFile+0x14
  6 01 00000064`c597e070 00007fff`dbb925c0     KERNELBASE!ConsoleCallServerGeneric+0xe9
  7 02 00000064`c597e1d0 00007fff`dbbefa75     KERNELBASE!GetConsoleInput+0xab3cc
  8 03 00000064`c597e260 00007ffe`a5dd1fb5     KERNELBASE!ReadConsoleInputW+0x15
  9 04 00000064`c597e2a0 00007fff`a6a2aa0a     0x00007ffe`a5dd1fb5
 10 05 00000064`c597e360 00007ffe`a5dd1ac7     System_Console!System.ConsoleKeyInfo System.ConsolePal::ReadKey(System.Boolean)$##60000DB+0xaa
 11 06 00000064`c597e420 00007ffe`a5dd1988     0x00007ffe`a5dd1ac7
 12 07 00000064`c597e490 00007fff`0591b8d3     0x00007ffe`a5dd1988
 13 08 00000064`c597e4e0 00007fff`05850b19     coreclr!CallDescrWorkerInternal+0x83
 14 09 (Inline Function) --------`--------     coreclr!CallDescrWorkerWithHandler+0x56
 15 0a 00000064`c597e520 00007fff`0584d730     coreclr!MethodDescCallSite::CallTargetWorker+0x2a1
 16 0b (Inline Function) --------`--------     coreclr!MethodDescCallSite::Call+0xb
 17 0c 00000064`c597e660 00007fff`05872fc6     coreclr!RunMainInternal+0x11c
 18 0d 00000064`c597e780 00007fff`058732fb     coreclr!RunMain+0xd2
 19 0e 00000064`c597e830 00007fff`057c9141     coreclr!Assembly::ExecuteMainMethod+0x1bf
 20 0f 00000064`c597eb00 00007fff`058de8b8     coreclr!CorHost2::ExecuteAssembly+0x281
 21 10 00000064`c597ec70 00007fff`549f2b76     coreclr!coreclr_execute_assembly+0xd8
 22 11 (Inline Function) --------`--------     hostpolicy!coreclr_t::execute_assembly+0x2a
 23 12 00000064`c597ed10 00007fff`549f2e5c     hostpolicy!run_app_for_context+0x596
 24 13 00000064`c597eea0 00007fff`549f379a     hostpolicy!run_app+0x3c
 25 14 00000064`c597eee0 00007fff`8d9db5c9     hostpolicy!corehost_main+0x15a
 26 15 00000064`c597efe0 00007fff`8d9de066     hostfxr!execute_app+0x2e9
 27 16 00000064`c597f0e0 00007fff`8d9e02ec     hostfxr!`anonymous namespace'::read_config_and_execute+0xa6
 28 17 00000064`c597f1d0 00007fff`8d9de644     hostfxr!fx_muxer_t::handle_exec_host_command+0x16c
 29 18 00000064`c597f280 00007fff`8d9d85a0     hostfxr!fx_muxer_t::execute+0x494
 30 19 00000064`c597f3c0 00007ff7`3043f998     hostfxr!hostfxr_main_startupinfo+0xa0
 31 1a 00000064`c597f4c0 00007ff7`3043fda6     apphost!exe_start+0x878
 32 1b 00000064`c597f690 00007ff7`304412e8     apphost!wmain+0x146
 33 1c (Inline Function) --------`--------     apphost!invoke_main+0x22
 34 1d 00000064`c597f700 00007fff`dd577344     apphost!__scrt_common_main_seh+0x10c
 35 1e 00000064`c597f740 00007fff`de5426b1     KERNEL32!BaseThreadInitThunk+0x14
 36 1f 00000064`c597f770 00000000`00000000     ntdll!RtlUserThreadStart+0x21
 37 
 38    1  Id: 2200.33d4 Suspend: 1 Teb: 00000064`c56e5000 Unfrozen
 39  # Child-SP          RetAddr               Call Site
 40 00 00000064`c5affac8 00007fff`de542e27     ntdll!NtWaitForWorkViaWorkerFactory+0x14
 41 01 00000064`c5affad0 00007fff`dd577344     ntdll!TppWorkerThread+0x2f7
 42 02 00000064`c5affdd0 00007fff`de5426b1     KERNEL32!BaseThreadInitThunk+0x14
 43 03 00000064`c5affe00 00000000`00000000     ntdll!RtlUserThreadStart+0x21
 44 
 45    2  Id: 2200.2750 Suspend: 1 Teb: 00000064`c56e7000 Unfrozen
 46  # Child-SP          RetAddr               Call Site
 47 00 00000064`c5c7f978 00007fff`de542e27     ntdll!NtWaitForWorkViaWorkerFactory+0x14
 48 01 00000064`c5c7f980 00007fff`dd577344     ntdll!TppWorkerThread+0x2f7
 49 02 00000064`c5c7fc80 00007fff`de5426b1     KERNEL32!BaseThreadInitThunk+0x14
 50 03 00000064`c5c7fcb0 00000000`00000000     ntdll!RtlUserThreadStart+0x21
 51 
 52    3  Id: 2200.2418 Suspend: 1 Teb: 00000064`c56e9000 Unfrozen
 53  # Child-SP          RetAddr               Call Site
 54 00 00000064`c5dff5f8 00007fff`de542e27     ntdll!NtWaitForWorkViaWorkerFactory+0x14
 55 01 00000064`c5dff600 00007fff`dd577344     ntdll!TppWorkerThread+0x2f7
 56 02 00000064`c5dff900 00007fff`de5426b1     KERNEL32!BaseThreadInitThunk+0x14
 57 03 00000064`c5dff930 00000000`00000000     ntdll!RtlUserThreadStart+0x21
 58 
 59    4  Id: 2200.748 Suspend: 1 Teb: 00000064`c56eb000 Unfrozen ".NET EventPipe"
 60  # Child-SP          RetAddr               Call Site
 61 00 00000064`c5f7f0a8 00007fff`dbb21d20     ntdll!NtWaitForMultipleObjects+0x14
 62 01 00000064`c5f7f0b0 00007fff`dbb21c1e     KERNELBASE!WaitForMultipleObjectsEx+0xf0
 63 02 00000064`c5f7f3a0 00007fff`058f073a     KERNELBASE!WaitForMultipleObjects+0xe
 64 03 00000064`c5f7f3e0 00007fff`058f06a2     coreclr!ds_ipc_poll+0x7e
 65 04 00000064`c5f7f660 00007fff`058f0564     coreclr!ds_ipc_stream_factory_get_next_available_stream+0x12a
 66 05 00000064`c5f7f730 00007fff`dd577344     coreclr!server_thread+0x54
 67 06 00000064`c5f7f7a0 00007fff`de5426b1     KERNEL32!BaseThreadInitThunk+0x14
 68 07 00000064`c5f7f7d0 00000000`00000000     ntdll!RtlUserThreadStart+0x21
 69 
 70    5  Id: 2200.3938 Suspend: 1 Teb: 00000064`c56ed000 Unfrozen ".NET Debugger"
 71  # Child-SP          RetAddr               Call Site
 72 00 00000064`c60ff958 00007fff`dbb21d20     ntdll!NtWaitForMultipleObjects+0x14
 73 01 00000064`c60ff960 00007fff`058e9c90     KERNELBASE!WaitForMultipleObjectsEx+0xf0
 74 02 00000064`c60ffc50 00007fff`058e9179     coreclr!DebuggerRCThread::MainLoop+0xe8
 75 03 00000064`c60ffd10 00007fff`058e8fab     coreclr!DebuggerRCThread::ThreadProc+0x139
 76 04 00000064`c60ffd70 00007fff`dd577344     coreclr!DebuggerRCThread::ThreadProcStatic+0x5b
 77 05 00000064`c60ffda0 00007fff`de5426b1     KERNEL32!BaseThreadInitThunk+0x14
 78 06 00000064`c60ffdd0 00000000`00000000     ntdll!RtlUserThreadStart+0x21
 79 
 80    6  Id: 2200.3a14 Suspend: 1 Teb: 00000064`c56ef000 Unfrozen ".NET Finalizer"(終結執行緒)
 81  # Child-SP          RetAddr               Call Site
 82 00 00000064`c627f688 00007fff`dbb21d20     ntdll!NtWaitForMultipleObjects+0x14
 83 01 00000064`c627f690 00007fff`05828f21     KERNELBASE!WaitForMultipleObjectsEx+0xf0
 84 02 00000064`c627f980 00007fff`05828d73     coreclr!FinalizerThread::WaitForFinalizerEvent+0x6d
 85 03 00000064`c627f9c0 00007fff`05874abd     coreclr!FinalizerThread::FinalizerThreadWorker+0x53
 86 04 (Inline Function) --------`--------     coreclr!ManagedThreadBase_DispatchInner+0xd
 87 05 00000064`c627fc10 00007fff`058749d3     coreclr!ManagedThreadBase_DispatchMiddle+0x85
 88 06 00000064`c627fcf0 00007fff`058daea1     coreclr!ManagedThreadBase_DispatchOuter+0xab
 89 07 (Inline Function) --------`--------     coreclr!ManagedThreadBase_NoADTransition+0x28
 90 08 (Inline Function) --------`--------     coreclr!ManagedThreadBase::FinalizerBase+0x28
 91 09 00000064`c627fd90 00007fff`dd577344     coreclr!FinalizerThread::FinalizerThreadStart+0x91
 92 0a 00000064`c627fea0 00007fff`de5426b1     KERNEL32!BaseThreadInitThunk+0x14
 93 0b 00000064`c627fed0 00000000`00000000     ntdll!RtlUserThreadStart+0x21
 94 
 95    7  Id: 2200.24fc Suspend: 1 Teb: 00000064`c56fd000 Unfrozen ".NET Tiered Compilation Worker"
 96  # Child-SP          RetAddr               Call Site
 97 00 00000064`c63ff7b8 00007fff`dbaf30ce     ntdll!NtWaitForSingleObject+0x14
 98 01 00000064`c63ff7c0 00007fff`05877d18     KERNELBASE!WaitForSingleObjectEx+0x8e
 99 02 (Inline Function) --------`--------     coreclr!CLREventWaitHelper2+0x6
100 03 00000064`c63ff860 00007fff`058f5f99     coreclr!CLREventWaitHelper+0x20
101 04 (Inline Function) --------`--------     coreclr!CLREventBase::WaitEx+0x12
102 05 (Inline Function) --------`--------     coreclr!CLREventBase::Wait+0x12
103 06 00000064`c63ff8c0 00007fff`058f5e3c     coreclr!TieredCompilationManager::BackgroundWorkerStart+0x119
104 07 00000064`c63ff910 00007fff`05874abd     coreclr!TieredCompilationManager::BackgroundWorkerBootstrapper1+0x5c
105 08 (Inline Function) --------`--------     coreclr!ManagedThreadBase_DispatchInner+0xd
106 09 00000064`c63ff950 00007fff`058749d3     coreclr!ManagedThreadBase_DispatchMiddle+0x85
107 0a 00000064`c63ffa30 00007fff`05904c8a     coreclr!ManagedThreadBase_DispatchOuter+0xab
108 0b (Inline Function) --------`--------     coreclr!ManagedThreadBase_FullTransition+0x24
109 0c (Inline Function) --------`--------     coreclr!ManagedThreadBase::KickOff+0x24
110 0d 00000064`c63ffad0 00007fff`dd577344     coreclr!TieredCompilationManager::BackgroundWorkerBootstrapper0+0x3a
111 0e 00000064`c63ffb20 00007fff`de5426b1     KERNEL32!BaseThreadInitThunk+0x14
112 0f 00000064`c63ffb50 00000000`00000000     ntdll!RtlUserThreadStart+0x21
113 
114    8  Id: 2200.20f4 Suspend: 1 Teb: 00000064`c56ff000 Unfrozen
115  # Child-SP          RetAddr               Call Site
116 00 00000064`c657efc0 00007fff`de51b44d     ntdll!RtlpAllocateHeap+0x3a
117 01 00000064`c657f210 00007fff`de5e88d8     ntdll!RtlpAllocateHeapInternal+0xa2d
118 02 00000064`c657f320 00007fff`de51d255     ntdll!RtlDebugAllocateHeap+0xe8
119 03 00000064`c657f380 00007fff`de51b44d     ntdll!RtlpAllocateHeap+0xf5
120 04 00000064`c657f5d0 00007fff`de538373     ntdll!RtlpAllocateHeapInternal+0xa2d
121 05 00000064`c657f6e0 00007fff`de5381c1     ntdll!LdrpGetNewTlsVector+0x37
122 06 00000064`c657f710 00007fff`de5077a7     ntdll!LdrpAllocateTls+0x61
123 07 00000064`c657f7e0 00007fff`de565064     ntdll!LdrpInitializeThread+0x6f
124 08 00000064`c657f8c0 00007fff`de564c43     ntdll!LdrpInitialize+0x408
125 09 00000064`c657f960 00007fff`de564bee     ntdll!LdrpInitialize+0x3b
126 0a 00000064`c657f990 00000000`00000000     ntdll!LdrInitializeThunk+0xe
127 
128 #  9  Id: 2200.3474 Suspend: 1 Teb: 00000064`c5701000 Unfrozen
129  # Child-SP          RetAddr               Call Site
130 00 00000064`c66ffca8 00007fff`de5bca0e     ntdll!DbgBreakPoint
131 01 00000064`c66ffcb0 00007fff`dd577344     ntdll!DbgUiRemoteBreakin+0x4e
132 02 00000064`c66ffce0 00007fff`de5426b1     KERNEL32!BaseThreadInitThunk+0x14
133 03 00000064`c66ffd10 00000000`00000000     ntdll!RtlUserThreadStart+0x21

                    紅色標註的就是終結器執行緒,負責執行帶有 Finalize 方法。

                2)、Windbg Preview 除錯
                    編譯專案,開啟【Windbg Preview】偵錯程式,依次點選【檔案】---【Launch executable】載入我們的專案檔案:ExampleCore_5_4.exe。進入偵錯程式後,我們使用【g】命令,繼續執行偵錯程式,直到我們的控制檯應用程式輸出“Press any key to GC1!”字樣,如圖:
                    

                    此時,我們點選偵錯程式的【break】按鈕,中斷偵錯程式的執行。
                    首先我們使用【!FinalizeQueue】命令檢視一下程序中可終結物件的狀態。

 1 0:001> !FinalizeQueue
 2 SyncBlocks to be cleaned up: 0
 3 Free-Threaded Interfaces to be released: 0
 4 MTA Interfaces to be released: 0
 5 STA Interfaces to be released: 0
 6 ----------------------------------
 7 
 8 Heap 0
 9 generation 0 has 0 objects (23681038090->23681038090)
10 generation 1 has 0 objects (23681038090->23681038090)
11 generation 2 has 0 objects (23681038090->23681038090)
12 Ready for finalization 0 objects (236810380f8->236810380f8)
13 ------------------------------
14 Statistics for all finalizable objects (including all objects ready for finalization):
15          Address               MT           Size
16     023685408fd0     7ffa497b2208            400 
17     023685409200     7ffa497b2b28             64 
18     023685409298     7ffa497b3d00             24 
19     0236854092b0     7ffa497b2b28             64 
20     023685409330     7ffa497b3d00             24 
21     023685409390     7ffa497b85d0            184 
22     0236854094c8     7ffa497b2b28             64 
23     023685409560     7ffa497b3d00             24 
24     023685409578     7ffa497b2b28             64 
25     0236854095f8     7ffa497b3d00             24 
26     023685409610     7ffa497b56f8             24 
27     023685409628     7ffa497b56f8             24 
28     023685409658     7ffa497b94b8             24 
29 
30 Statistics:
31           MT Count TotalSize Class Name
32 7ffa497b94b8     1        24 ExampleCore_5_4.NativeEvent
33 7ffa497b56f8     2        48 System.WeakReference<System.Diagnostics.Tracing.EventSource>
34 7ffa497b3d00     4        96 System.WeakReference<System.Diagnostics.Tracing.EventProvider>
35 7ffa497b85d0     1       184 System.Diagnostics.Tracing.NativeRuntimeEventSource
36 7ffa497b2b28     4       256 System.Diagnostics.Tracing.EventSource+OverrideEventProvider
37 7ffa497b2208     1       400 System.Diagnostics.Tracing.RuntimeEventSource
38 Total 13 objects, 1,008 bytes

                    在輸出的結果中,首先:給出了每一代的終結佇列,並給出了每個終結佇列本身的地址範圍,比如:第 0 代終結佇列的起始地址是 23681038090,結束地址是 23681038090。由於沒有任何元素,開始和結束地址是一樣的。我們可以使用【dp 23681038090】檢視它的內容,其實就是【Statistics】統計的內容,有多少個物件例項,就儲存了多少項。

1 0:001> dp 23681038090
2 00000236`81038090  00000236`85408fd0 00000236`85409200
3 00000236`810380a0  00000236`85409298 00000236`854092b0
4 00000236`810380b0  00000236`85409330 00000236`85409390
5 00000236`810380c0  00000236`854094c8 00000236`85409560
6 00000236`810380d0  00000236`85409578 00000236`854095f8
7 00000236`810380e0  00000236`85409610 00000236`85409628
8 00000236`810380f0  00000236`85409658 baadf00d`baadf00d
9 00000236`81038100  baadf00d`baadf00d baadf00d`baadf00d

                    我們在【Statistics】項的【Count】列的值總和是 13,我紅色標註的也是 13 專案,最後3項不算。針對每個地址,我們可以使用【!do】命令或者【!DumpObj】命令來驗證。我只輸出標紅的最後一個(棧的是從高地址到地地址分配,定義越早,地址越大),它是棧頂的地址,這項就是我們定義的 ExampleCore_5_4.NativeEvent

 1 0:001> !do 00000236`85409658
 2 Name:        ExampleCore_5_4.NativeEvent
 3 MethodTable: 00007ffa497b94b8
 4 EEClass:     00007ffa497c1ff0
 5 Tracked Type: false
 6 Size:        24(0x18) bytes
 7 File:        E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_5_4\bin\Debug\net8.0\ExampleCore_5_4.dll
 8 Fields:
 9               MT    Field   Offset                 Type VT     Attr            Value Name
10 00007ffa496e70a0  4000001        8        System.IntPtr  1 instance 000000000000028C _nativeHandle

                    從【!FinalizeQueue】命令輸出,我們可以看到第 0 代、第 1 代和第 2 代沒有任何可終結的物件。

                    【!FinalizeQueue】命令輸出中另一個有用的資訊就是 F-Reachable 佇列。如下所示:

1 Ready for finalization 0 objects (236810380f8->236810380f8)

                    這個資訊表示此時沒有任何物件需要執行終結操作。這是正確的,因為垃圾收集器還沒有啟動。

                    【!FinalizeQueue】命令輸出中最後一部分是一些統計資訊,其中包括在終結佇列中或者 F-Reachable 佇列中所有的物件。

                    如果我們想知道程序中包含的所有執行緒的棧回溯,包括終結執行緒,我們可以使用【~*kn】命令。

 1 0:001> ~*kn
 2 
 3    0  Id: 39bc.2f20 Suspend: 1 Teb: 0000007d`196e4000 Unfrozen
 4  # Child-SP          RetAddr               Call Site
 5 00 0000007d`1997e528 00007ffb`76617861     ntdll!NtDeviceIoControlFile+0x14
 6 01 0000007d`1997e530 00007ffb`766c25c0     KERNELBASE!ConsoleCallServerGeneric+0xe9
 7 02 0000007d`1997e690 00007ffb`7671fa75     KERNELBASE!GetConsoleInput+0xab3cc
 8 03 0000007d`1997e720 00007ffa`a7c77893     KERNELBASE!ReadConsoleInputW+0x15
 9 04 0000007d`1997e760 00007ffa`a7c7aa0a     System_Console!Interop.Kernel32.ReadConsoleInput+0x83 [/_/src/libraries/System.Console/src/Microsoft.Interop.LibraryImportGenerator/Microsoft.Interop.LibraryImportGenerator/LibraryImports.g.cs @ 470] 
10 05 0000007d`1997e850 00007ffa`49701a7d     System_Console!System.ConsolePal.ReadKey+0xaa [/_/src/libraries/System.Console/src/System/ConsolePal.Windows.cs @ 334] 
11 06 0000007d`1997e910 00007ffa`49701988     ExampleCore_5_4_1!ExampleCore_5_4_1.Program.Run+0x7d [E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_5_4_1\Program.cs @ 57] 
12 07 0000007d`1997e980 00007ffa`a928b8d3     ExampleCore_5_4_1!ExampleCore_5_4_1.Program.Main+0x58 [E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_5_4_1\Program.cs @ 45] 
13 08 0000007d`1997e9c0 00007ffa`a91c0b19     coreclr!CallDescrWorkerInternal+0x83 [D:\a\_work\1\s\src\coreclr\vm\amd64\CallDescrWorkerAMD64.asm @ 100] 
14 09 (Inline Function) --------`--------     coreclr!CallDescrWorkerWithHandler+0x56 [D:\a\_work\1\s\src\coreclr\vm\callhelpers.cpp @ 67] 
15 0a 0000007d`1997ea00 00007ffa`a91bd730     coreclr!MethodDescCallSite::CallTargetWorker+0x2a1 [D:\a\_work\1\s\src\coreclr\vm\callhelpers.cpp @ 570] 
16 0b (Inline Function) --------`--------     coreclr!MethodDescCallSite::Call+0xb [D:\a\_work\1\s\src\coreclr\vm\callhelpers.h @ 458] 
17 0c 0000007d`1997eb40 00007ffa`a91e2fc6     coreclr!RunMainInternal+0x11c [D:\a\_work\1\s\src\coreclr\vm\assembly.cpp @ 1304] 
18 0d 0000007d`1997ec60 00007ffa`a91e32fb     coreclr!RunMain+0xd2 [D:\a\_work\1\s\src\coreclr\vm\assembly.cpp @ 1375] 
19 0e 0000007d`1997ed10 00007ffa`a9139141     coreclr!Assembly::ExecuteMainMethod+0x1bf [D:\a\_work\1\s\src\coreclr\vm\assembly.cpp @ 1504] 
20 0f 0000007d`1997efe0 00007ffa`a924e8b8     coreclr!CorHost2::ExecuteAssembly+0x281 [D:\a\_work\1\s\src\coreclr\vm\corhost.cpp @ 349] 
21 10 0000007d`1997f150 00007ffa`a9642b76     coreclr!coreclr_execute_assembly+0xd8 [D:\a\_work\1\s\src\coreclr\dlls\mscoree\exports.cpp @ 504] 
22 11 (Inline Function) --------`--------     hostpolicy!coreclr_t::execute_assembly+0x2a [D:\a\_work\1\s\src\native\corehost\hostpolicy\coreclr.cpp @ 109] 
23 12 0000007d`1997f1f0 00007ffa`a9642e5c     hostpolicy!run_app_for_context+0x596 [D:\a\_work\1\s\src\native\corehost\hostpolicy\hostpolicy.cpp @ 256] 
24 13 0000007d`1997f380 00007ffa`a964379a     hostpolicy!run_app+0x3c [D:\a\_work\1\s\src\native\corehost\hostpolicy\hostpolicy.cpp @ 285] 
25 14 0000007d`1997f3c0 00007ffa`a969b5c9     hostpolicy!corehost_main+0x15a [D:\a\_work\1\s\src\native\corehost\hostpolicy\hostpolicy.cpp @ 426] 
26 15 0000007d`1997f4c0 00007ffa`a969e066     hostfxr!execute_app+0x2e9 [D:\a\_work\1\s\src\native\corehost\fxr\fx_muxer.cpp @ 145] 
27 16 0000007d`1997f5c0 00007ffa`a96a02ec     hostfxr!`anonymous namespace'::read_config_and_execute+0xa6 [D:\a\_work\1\s\src\native\corehost\fxr\fx_muxer.cpp @ 532] 
28 17 0000007d`1997f6b0 00007ffa`a969e644     hostfxr!fx_muxer_t::handle_exec_host_command+0x16c [D:\a\_work\1\s\src\native\corehost\fxr\fx_muxer.cpp @ 1007] 
29 18 0000007d`1997f760 00007ffa`a96985a0     hostfxr!fx_muxer_t::execute+0x494 [D:\a\_work\1\s\src\native\corehost\fxr\fx_muxer.cpp @ 578] 
30 19 0000007d`1997f8a0 00007ff6`50a1f998     hostfxr!hostfxr_main_startupinfo+0xa0 [D:\a\_work\1\s\src\native\corehost\fxr\hostfxr.cpp @ 62] 
31 1a 0000007d`1997f9a0 00007ff6`50a1fda6     apphost!exe_start+0x878 [D:\a\_work\1\s\src\native\corehost\corehost.cpp @ 240] 
32 1b 0000007d`1997fb70 00007ff6`50a212e8     apphost!wmain+0x146 [D:\a\_work\1\s\src\native\corehost\corehost.cpp @ 311] 
33 1c (Inline Function) --------`--------     apphost!invoke_main+0x22 [D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl @ 90] 
34 1d 0000007d`1997fbe0 00007ffb`781a7344     apphost!__scrt_common_main_seh+0x10c [D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl @ 288] 
35 1e 0000007d`1997fc20 00007ffb`78d426b1     KERNEL32!BaseThreadInitThunk+0x14
36 1f 0000007d`1997fc50 00000000`00000000     ntdll!RtlUserThreadStart+0x21
37 
38 #  1  Id: 39bc.3210 Suspend: 1 Teb: 0000007d`196f8000 Unfrozen
39  # Child-SP          RetAddr               Call Site
40 00 0000007d`19affc28 00007ffb`78dbca0e     ntdll!DbgBreakPoint
41 01 0000007d`19affc30 00007ffb`781a7344     ntdll!DbgUiRemoteBreakin+0x4e
42 02 0000007d`19affc60 00007ffb`78d426b1     KERNEL32!BaseThreadInitThunk+0x14
43 03 0000007d`19affc90 00000000`00000000     ntdll!RtlUserThreadStart+0x21
44 
45    4  Id: 39bc.1e08 Suspend: 1 Teb: 0000007d`196ec000 Unfrozen ".NET EventPipe"
46  # Child-SP          RetAddr               Call Site
47 00 0000007d`19f7f658 00007ffb`76651d20     ntdll!NtWaitForMultipleObjects+0x14
48 01 0000007d`19f7f660 00007ffb`76651c1e     KERNELBASE!WaitForMultipleObjectsEx+0xf0
49 02 0000007d`19f7f950 00007ffa`a926073a     KERNELBASE!WaitForMultipleObjects+0xe
50 03 0000007d`19f7f990 00007ffa`a92606a2     coreclr!ds_ipc_poll+0x7e [D:\a\_work\1\s\src\native\eventpipe\ds-ipc-pal-namedpipe.c @ 240] 
51 04 0000007d`19f7fc10 00007ffa`a9260564     coreclr!ds_ipc_stream_factory_get_next_available_stream+0x12a [D:\a\_work\1\s\src\native\eventpipe\ds-ipc.c @ 402] 
52 05 0000007d`19f7fce0 00007ffb`781a7344     coreclr!server_thread+0x54 [D:\a\_work\1\s\src\native\eventpipe\ds-server.c @ 129] 
53 06 0000007d`19f7fd50 00007ffb`78d426b1     KERNEL32!BaseThreadInitThunk+0x14
54 07 0000007d`19f7fd80 00000000`00000000     ntdll!RtlUserThreadStart+0x21
55 
56    5  Id: 39bc.9c8 Suspend: 1 Teb: 0000007d`196ee000 Unfrozen ".NET Debugger"
57  # Child-SP          RetAddr               Call Site
58 00 0000007d`1a0ff7e8 00007ffb`76651d20     ntdll!NtWaitForMultipleObjects+0x14
59 01 0000007d`1a0ff7f0 00007ffa`a9259c90     KERNELBASE!WaitForMultipleObjectsEx+0xf0
60 02 0000007d`1a0ffae0 00007ffa`a9259179     coreclr!DebuggerRCThread::MainLoop+0xe8 [D:\a\_work\1\s\src\coreclr\debug\ee\rcthread.cpp @ 927] 
61 03 0000007d`1a0ffba0 00007ffa`a9258fab     coreclr!DebuggerRCThread::ThreadProc+0x139 [D:\a\_work\1\s\src\coreclr\debug\ee\rcthread.cpp @ 730] 
62 04 0000007d`1a0ffc00 00007ffb`781a7344     coreclr!DebuggerRCThread::ThreadProcStatic+0x5b [D:\a\_work\1\s\src\coreclr\debug\ee\rcthread.cpp @ 1321] 
63 05 0000007d`1a0ffc30 00007ffb`78d426b1     KERNEL32!BaseThreadInitThunk+0x14
64 06 0000007d`1a0ffc60 00000000`00000000     ntdll!RtlUserThreadStart+0x21
65 
66    6  Id: 39bc.14d4 Suspend: 1 Teb: 0000007d`196f0000 Unfrozen ".NET Finalizer"(終結執行緒)
67  # Child-SP          RetAddr               Call Site
68 00 0000007d`1a27f3e8 00007ffb`76651d20     ntdll!NtWaitForMultipleObjects+0x14
69 01 0000007d`1a27f3f0 00007ffa`a9198f21     KERNELBASE!WaitForMultipleObjectsEx+0xf0
70 02 0000007d`1a27f6e0 00007ffa`a9198d73     coreclr!FinalizerThread::WaitForFinalizerEvent+0x6d [D:\a\_work\1\s\src\coreclr\vm\finalizerthread.cpp @ 173] 
71 03 0000007d`1a27f720 00007ffa`a91e4abd     coreclr!FinalizerThread::FinalizerThreadWorker+0x53 [D:\a\_work\1\s\src\coreclr\vm\finalizerthread.cpp @ 262] 
72 04 (Inline Function) --------`--------     coreclr!ManagedThreadBase_DispatchInner+0xd [D:\a\_work\1\s\src\coreclr\vm\threads.cpp @ 7222] 
73 05 0000007d`1a27f970 00007ffa`a91e49d3     coreclr!ManagedThreadBase_DispatchMiddle+0x85 [D:\a\_work\1\s\src\coreclr\vm\threads.cpp @ 7266] 
74 06 0000007d`1a27fa50 00007ffa`a924aea1     coreclr!ManagedThreadBase_DispatchOuter+0xab [D:\a\_work\1\s\src\coreclr\vm\threads.cpp @ 7425] 
75 07 (Inline Function) --------`--------     coreclr!ManagedThreadBase_NoADTransition+0x28 [D:\a\_work\1\s\src\coreclr\vm\threads.cpp @ 7494] 
76 08 (Inline Function) --------`--------     coreclr!ManagedThreadBase::FinalizerBase+0x28 [D:\a\_work\1\s\src\coreclr\vm\threads.cpp @ 7513] 
77 09 0000007d`1a27faf0 00007ffb`781a7344     coreclr!FinalizerThread::FinalizerThreadStart+0x91 [D:\a\_work\1\s\src\coreclr\vm\finalizerthread.cpp @ 403] 
78 0a 0000007d`1a27fc00 00007ffb`78d426b1     KERNEL32!BaseThreadInitThunk+0x14
79 0b 0000007d`1a27fc30 00000000`00000000     ntdll!RtlUserThreadStart+0x21

                    紅色標註的就是終結執行緒呼叫棧,這個執行緒正在等待終結器事件(coreclr!FinalizerThread::WaitForFinalizerEvent)。coreclr!FinalizerThread::FinalizerThreadWorker這個方法的返回地址是 00007ffa`a91e4abd ,我們在這個方法上設定一個斷點,當終結執行緒執行工作時就會觸發這個斷點。我們使用【bp 00007ffa`a9198d73】命令,設定斷點。

1 0:001> bp 00007ffa`a91e4abd

                    使用【g】命令恢復程式的執行,在控制檯程式中按任意鍵觸發第一次垃圾回收。直到輸出“Press any key to GC2!”字樣。

1 0:007> g
2 Breakpoint 0 hit
3 coreclr!ManagedThreadBase_DispatchMiddle+0x85:
4 00007ffa`b30e4abd 90              nop

                    觸發了斷點。我們再次執行【!FinalizeQueue】命令。

 1 0:007> !FinalizeQueue
 2 SyncBlocks to be cleaned up: 0
 3 Free-Threaded Interfaces to be released: 0
 4 MTA Interfaces to be released: 0
 5 STA Interfaces to be released: 0
 6 ----------------------------------
 7 
 8 Heap 0
 9 generation 0 has 13 objects (26ba90a8080->26ba90a80e8)(第 0 代有 13 個可終結的物件)
10 generation 1 has 0 objects (26ba90a8080->26ba90a8080)
11 generation 2 has 0 objects (26ba90a8080->26ba90a8080)
12 Ready for finalization 0 objects (26ba90a80e8->26ba90a80e8)
13 ------------------------------
14 Statistics for all finalizable objects (including all objects ready for finalization):
15          Address               MT           Size
16     026bad408fd0     7ffa536e2208            400 
17     026bad409200     7ffa536e2b28             64 
18     026bad409298     7ffa536e3d00             24 
19     026bad4092b0     7ffa536e2b28             64 
20     026bad409330     7ffa536e3d00             24 
21     026bad409390     7ffa536e85d0            184 
22     026bad4094c8     7ffa536e2b28             64 
23     026bad409560     7ffa536e3d00             24 
24     026bad409578     7ffa536e2b28             64 
25     026bad4095f8     7ffa536e3d00             24 
26     026bad409610     7ffa536e56f8             24 
27     026bad409628     7ffa536e56f8             24 
28     026bad409658     7ffa536e94b8             24 
29 
30 Statistics:
31           MT Count TotalSize Class Name
32 7ffa536e94b8     1        24 ExampleCore_5_4.NativeEvent
33 7ffa536e56f8     2        48 System.WeakReference<System.Diagnostics.Tracing.EventSource>
34 7ffa536e3d00     4        96 System.WeakReference<System.Diagnostics.Tracing.EventProvider>
35 7ffa536e85d0     1       184 System.Diagnostics.Tracing.NativeRuntimeEventSource
36 7ffa536e2b28     4       256 System.Diagnostics.Tracing.EventSource+OverrideEventProvider
37 7ffa536e2208     1       400 System.Diagnostics.Tracing.RuntimeEventSource
38 Total 13 objects, 1,008 bytes

                第 0 代有 13 個可終結的物件,第 1 代和第 2 代沒有任何可終結的物件。
                當我們繼續【g】恢復程式的執行,在控制檯程式中按任意鍵觸發第二次垃圾回收。一直到控制檯輸出“Press any key to Exit!”字樣。

                回到偵錯程式,點選【break】按鈕繼續進入到中斷模式,繼續執行【!FinalizeQueue】命令。

 1 0:001> !FinalizeQueue
 2 SyncBlocks to be cleaned up: 0
 3 Free-Threaded Interfaces to be released: 0
 4 MTA Interfaces to be released: 0
 5 STA Interfaces to be released: 0
 6 ----------------------------------
 7 
 8 Heap 0
 9 generation 0 has 0 objects (1ea05688118->1ea05688118)(第 0 代沒有了)
10 generation 1 has 13 objects (1ea056880b0->1ea05688118)(升級到第 1 代了,物件的生命週期變長了)
11 generation 2 has 0 objects (1ea056880b0->1ea056880b0)
12 Ready for finalization 0 objects (1ea05688118->1ea05688118)
13 ------------------------------
14 Statistics for all finalizable objects (including all objects ready for finalization):
15          Address               MT           Size
16     01ea09c08fd0     7ffa53742208            400 
17     01ea09c09200     7ffa53742b28             64 
18     01ea09c09298     7ffa53743d00             24 
19     01ea09c092b0     7ffa53742b28             64 
20     01ea09c09330     7ffa53743d00             24 
21     01ea09c09390     7ffa537485d0            184 
22     01ea09c094c8     7ffa53742b28             64 
23     01ea09c09560     7ffa53743d00             24 
24     01ea09c09578     7ffa53742b28             64 
25     01ea09c095f8     7ffa53743d00             24 
26     01ea09c09610     7ffa537456f8             24 
27     01ea09c09628     7ffa537456f8             24 
28     01ea09c09658     7ffa537494a8             24 
29 
30 Statistics:
31           MT Count TotalSize Class Name
32 7ffa537494a8     1        24 ExampleCore_5_4.NativeEvent
33 7ffa537456f8     2        48 System.WeakReference<System.Diagnostics.Tracing.EventSource>
34 7ffa53743d00     4        96 System.WeakReference<System.Diagnostics.Tracing.EventProvider>
35 7ffa537485d0     1       184 System.Diagnostics.Tracing.NativeRuntimeEventSource
36 7ffa53742b28     4       256 System.Diagnostics.Tracing.EventSource+OverrideEventProvider
37 7ffa53742208     1       400 System.Diagnostics.Tracing.RuntimeEventSource
38 Total 13 objects, 1,008 bytes

                我們看到經過第 2 次垃圾回收,那些物件升級到第 1 代了。此時,我們可以使用【!dumpheap -type NativeEvent】找到 NativeEvent 型別的記憶體地址,然後再使用【!gcroot】命令,還可以找到 NativeEvent型別是有根引用的。

1 0:007> !DumpHeap -type NativeEvent
2          Address               MT           Size
3     0179a9409658     7ffa51a294a8             24 
4 
5 Statistics:
6           MT Count TotalSize Class Name
7 7ffa51a294a8     1        24 ExampleCore_5_4.NativeEvent
8 Total 1 objects, 24 bytes

                0179a9409658 這個地址就是 ExampleCore_5_4.NativeEvent 型別,我看看還有沒有根引用。

 1 0:007> !gcroot 0179a9409658 
 2 Caching GC roots, this may take a while.
 3 Subsequent runs of this command will be faster.
 4 
 5 Thread 3204:
 6     1ff797ebd0 7ffa51971ab7 ExampleCore_5_4.Program.Run() [E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_5_4\Program.cs @ 54]
 7         rbp-10: 0000001ff797ec20
 8           -> 0179a9409658     ExampleCore_5_4.NativeEvent 
 9 
10         rbp-8: 0000001ff797ec28
11           -> 0179a9409658     ExampleCore_5_4.NativeEvent 
12 
13 Found 2 unique roots.

                說明不能回收。

                我們繼續【g】執行偵錯程式。在控制檯程式按任意鍵。我們再次執行【 !gcroot 0179a9409658 】。

1 0:006> !gcroot 0179a9409658 
2 Caching GC roots, this may take a while.
3 Subsequent runs of this command will be faster.
4 
5 Found 0 unique roots

                沒有任何引用了,可以執行垃圾回收操作了,終結執行緒就會在合適時機執行。


        3.2.4、回收 GC 記憶體
            如果在第 0 代和第 1 代上執行的垃圾回收操作會引起託管堆上出現記憶體空間縫隙,那麼 GC 將執行對所有的活躍物件的緊縮操作,這就可以使物件地址相鄰,並把託管堆上所有的空閒空間合併成一個更大的記憶體空間,這個新的記憶體空間會緊跟在最後一個活躍物件之後。
            以下是一個示意圖:
            

            在上圖中,在託管堆上的初始狀態中包含了 5 個存在根引用的物件(依次A 到 E)。在執行過程的某個時刻,物件 B 和物件 D 沒有任何根引用了,將在下一次執行垃圾回收的時候被清理掉。當垃圾操作執行時,會回收物件 B 和物件 D 的空間,這將導致託管堆上出現空間碎片。為了消除空間碎片,GC 會把剩下的所有活躍的物件(物件A、C 和 E)進行緊縮,並將多個空閒的記憶體塊(儲存物件B和 D 的記憶體)合併成一個大的空閒塊。最後,根據物件的緊縮和合並的結果來更新當前記憶體分配指標。
            注意:
              由於達到了所有 3 個代的閾值而使的所有代中的物件都被收集就是【完全垃圾收集】,盡在第 0 代或者第 0 代和第 1 代進行垃圾收集就是【部分垃圾收集】。由於執行緊縮操作的開銷和物件的大小成正比(物件越大,緊縮操作的開銷越高),所以在託管堆上,有大物件堆和小物件堆之分。


        3.2.5、大物件堆
            A、基礎知識
                大物件堆(LOH)包含的物件通常大於或者等於 85 000 個位元組。將這種大小的物件單獨放入一個堆是有原因的:在垃圾收集的緊縮階段,在對某個物件執行緊縮操作時的開銷是與物件的大小成正比的。因此,沒有將大物件放在標準的堆上,而是建立了 LOH。LOH 最好被視為第 2 代記憶體空間的擴充套件,並對 LOH 中的物件的收集操作只有在收集完第 2 代中的物件之後才會進行,這也就意味著對 LOH 中物件的收集操作只會在【完全垃圾收集】中進行。
                因為大物件進行壓縮操作的開銷十分大,所以 GC 會避免在 LOH 上進行緊縮操作,取而代之的就是執行【標記清除】。在這個操作中會維持一個空閒連結串列,用於跟蹤 LOH 記憶體段中可用記憶體。如圖:
                

                雖然 LOH 沒有執行任何緊縮操作,但是它會合並相鄰的空閒記憶體塊,並把它新增到空閒連結串列中。

                總結:LOH堆也就是大物件堆,既沒有代的機制,也沒有壓縮的機制,只有“標記清除”,即:GC 觸發時,只會將一個物件標記成 Free 物件。這種 Free 可供後續分配的物件,可以說,以後有新物件產生,會首先存放在 Free 塊中。

                當我們透過偵錯程式擴充套件命令檢視 LOH 的時候,發現這個物件會有很多大小小於 85000 的物件,這些物件是有 CLR 堆管理器放在 LOH 上的,有著特殊的作用。通常來說,你可以看到一些由 GC 專門使用的並且小於 85000 位元組的物件。
                
            B、眼見為實
                除錯原始碼:ExampleCore_5_5
                除錯任務:檢視大物件如何在大物件堆上分配和回收
                1)、NTSD 除錯
                    編譯專案,開啟【Visual Studio 2022 Developer Command Prompt v17.9.6】命令列工具,輸入命令【NTSD E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_5_5\bin\Debug\net8.0\ExampleCore_5_5.exe】開啟偵錯程式。
                    進入偵錯程式後,我們直接使用【g】命令執行偵錯程式,直到偵錯程式輸出“1、物件已經分配,請檢視託管堆!”,並且除錯進入中斷模式,效果如圖:
                    

                    此時,說明記憶體已經分配了,我們首先使用【!eeheap -gc】命令檢視 GC 的詳情,主要關注 LOH(大物件堆)。

 1 0:000> !eeheap -gc
 2 Number of GC Heaps: 1
 3 generation 0 starts at 0x00000142C0C00028
 4 generation 1 starts at 0x00000142C0800028
 5 generation 2 starts at 0x0000018352CE0008
 6 ephemeral segment allocation context: none
 7          segment             begin         allocated         committed    allocated size    committed size
 8 generation 0:
 9 00000182D21DF320  00000142C0C00028  00000142C0C00028  00000142C0C11000  0x0(0)  0x10fd8(69592)
10 generation 1:
11 00000182D21DF270  00000142C0800028  00000142C0800028  00000142C0801000  0x0(0)  0xfd8(4056)
12 generation 2:
13 00000142BE0CC1D0  0000018352CE0008  0000018352CE0A48  0000018352CF0000  0xa40(2624)  0xfff8(65528)
14 00000182D21DF1C0  00000142C0400028  00000142C0400028  00000142C0401000  0x0(0)  0xfd8(4056)
15 Large object heap starts at 0x0000000000000000
16          segment             begin         allocated         committed    allocated size    committed size
17 00000182D21DF3D0  00000142C1000028  00000142C10D0CA8  00000142C10D1000  0xd0c80(855168)  0xd0fd8(856024)
18 Pinned object heap starts at 0x0000000000000000
19 00000182D21DEC40  00000142BE400028  00000142BE404018  00000142BE411000  0x3ff0(16368)  0x10fd8(69592)
20 Total Allocated Size:              Size: 0xd56b0 (874160) bytes.
21 Total Committed Size:              Size: 0xf3f58 (999256) bytes.
22 ------------------------------
23 GC Allocated Heap Size:    Size: 0xd56b0 (874160) bytes.
24 GC Committed Heap Size:    Size: 0xf3f58 (999256) bytes.

                    紅色標註的就是 LOH,從此我們就能得到 LOH 的開始地址和結束地址,分別是:00000142C1000028-00000142C10D0CA8,有了這個地址範圍,我們就可以檢視 LOH 有什麼物件,使用命令【!DumpHeap 00000142C1000028 00000142C10D0CA8】檢視這個地址範圍內的所有物件。

 1 0:000> !DumpHeap 00000142C1000028 00000142C10D0CA8
 2          Address               MT     Size
 3 00000142c1000028 00000142bc5e00f0       32 Free
 4 00000142c1000048 00007ffcea4c9430   185024(這就是 byte1 變數)
 5 00000142c102d308 00000142bc5e00f0       32 Free
 6 00000142c102d328 00007ffcea4c9430   285024(這就是 byte2 變數)
 7 00000142c1072c88 00000142bc5e00f0       32 Free
 8 00000142c1072ca8 00007ffcea4c9430   385024(這就是 byte3 變數)
 9 
10 Statistics:
11               MT    Count    TotalSize Class Name
12 00000142bc5e00f0        3           96      Free
13 00007ffcea4c9430        3       855072 System.Byte[]
14 Total 6 objects

                    紅色標註的就是我們在 Test() 方法內部初始化的 3 個變數,它們的大小都是大於 85000 位元組的,所以都會分配在 LOH 上。
                    我們【g】繼續執行偵錯程式,直到偵錯程式輸出“2、GC 已經觸發,請檢視託管堆中的 byte2”,執行垃圾回收,byte2 變數沒有根引用,所以會被回收,其它兩個變數儲存。

1 0:000> g
2 2、GC 已經觸發,請檢視託管堆中的 byte2
3 (292c.23a4): Break instruction exception - code 80000003 (first chance)
4 KERNELBASE!wil::details::DebugBreak+0x2:
5 00007ffd`ccacb502 cc              int     3

                    此時,我們再次執行【!DumpHeap 00000142C1000028 00000142C10D0CA8】命令檢視 LOH 的情況。

 1 0:000> !DumpHeap 00000142C1000028 00000142C10D0CA8
 2          Address               MT     Size
 3 00000142c1000028 00000142bc5e00f0       32 Free
 4 00000142c1000048 00007ffcea4c9430   185024(變數 byte1 還在)
 5 00000142c102d308 00000142bc5e00f0   285088 Free(變數 byte2 已經被回收了)
 6 00000142c1072ca8 00007ffcea4c9430   385024(變數 byte3 還在)
 7 
 8 Statistics:
 9               MT    Count    TotalSize Class Name
10 00000142bc5e00f0        2       285120      Free
11 00007ffcea4c9430        2       570048 System.Byte[]
12 Total 4 objects

                    上面寫的很清楚了,byte2 變數已經被回收了,它剩下的空間標記為“Free”,如果此時,我們宣告一個物件的大小小於 285088 這數值,這塊空間就會直接被使用。
                    【g】命令繼續執行偵錯程式,直到偵錯程式輸出“3、已分配 byte4,檢視是否 Free 塊中”字樣,我們可以再次檢視 LOH 的情況。

1 0:000> g
2 3、已分配 byte4,檢視是否 Free 塊中
3 (292c.23a4): Break instruction exception - code 80000003 (first chance)
4 KERNELBASE!wil::details::DebugBreak+0x2:
5 00007ffd`ccacb502 cc              int     3

                    再次執行【!DumpHeap 00000142C1000028 00000142C10D0CA8】命令,看看 LOH 的情況。

 1 0:000> !DumpHeap 00000142C1000028 00000142C10D0CA8
 2          Address               MT     Size
 3 00000142c1000028 00000142bc5e00f0       32 Free
 4 00000142c1000048 00007ffcea4c9430   185024(變數 byte1 還在)
 5 00000142c102d308 00000142bc5e00f0       32 Free
 6 00000142c102d328 00007ffcea4c9430   280024(我們重新宣告的變數 byte4,大小是280000)
 7 00000142c1071900 00000142bc5e00f0     5032 Free
 8 00000142c1072ca8 00007ffcea4c9430   385024(變數 byte3 還在)
 9 
10 Statistics:
11               MT    Count    TotalSize Class Name
12 00000142bc5e00f0        3         5096      Free
13 00007ffcea4c9430        3       850072 System.Byte[]
14 Total 6 objects

                    上面的內容很好的證明了我們的說法,紅色標註的已經說明了問題,分配的 byte4 物件大小正好在 Free 塊中,所以就把 byte4 直接儲存了。

                2)、Windbg Preview 除錯
                    編譯專案,開啟【Windbg Preview】偵錯程式,依次點選【檔案】---【Launch executable】,載入我們的專案檔案:ExampleCore_5_5.exe,進入我們的偵錯程式。
                    進入偵錯程式後,我們直接使用【g】命令執行偵錯程式,偵錯程式會自己中斷,並且,我們得控制檯程式會輸出“1、物件已經分配,請檢視託管堆!”字樣。
                    我們使用【!eeheap -gc】查詢 LOH 的地址,有了地址才可以才看堆上的物件。

 1 0:000> !eeheap -gc
 2 
 3 ========================================
 4 Number of GC Heaps: 1
 5 ----------------------------------------
 6 Small object heap
 7          segment            begin        allocated        committed allocated size   committed size  
 8 generation 0:
 9     02cf5e8ff320     028f4d400028     028f4d40a630     028f4d411000 0xa608 (42504)   0x11000 (69632) 
10 generation 1:
11     02cf5e8ff270     028f4d000028     028f4d000028     028f4d001000                  0x1000 (4096)   
12 generation 2:
13     02cf5e8ff1c0     028f4cc00028     028f4cc00028     028f4cc01000                  0x1000 (4096)   
14 NonGC heap
15          segment            begin        allocated        committed allocated size   committed size  
16     028f48ce81e0     02cfdf400008     02cfdf400a48     02cfdf410000 0xa40 (2624)     0x10000 (65536) 
17 Large object heap
18          segment            begin        allocated        committed allocated size   committed size  
19     02cf5e8ff3d0     028f4d800028     028f4d8d0ca8     028f4d8d1000 0xd0c80 (855168) 0xd1000 (856064)
20 Pinned object heap
21          segment            begin        allocated        committed allocated size   committed size  
22     02cf5e8fec40     028f4ac00028     028f4ac04018     028f4ac11000 0x3ff0 (16368)   0x11000 (69632) 
23 ------------------------------
24 GC Allocated Heap Size:    Size: 0xdfcb8 (916664) bytes.
25 GC Committed Heap Size:    Size: 0x105000 (1069056) bytes.

                    紅色標註的就是 LOH 區域,由於我們的物件最小都是 85000,所以其他的堆都不用關心,直接檢視 LOH。
                    我們看到 LOH 的起始地址是 028f4d800028,我們就可以使用【!DumpHeap 028f4d800028】命令,檢視LOH 有什麼物件了。

 1 0:000> !dumpheap 028f4d800028
 2          Address               MT           Size
 3     028f4d800028     028f48d0c320             32 Free
 4     028f4d800048     7ffea05e9430        185,024 (byte1)
 5     028f4d82d308     028f48d0c320             32 Free
 6     028f4d82d328     7ffea05e9430        285,024 (byte2)
 7     028f4d872c88     028f48d0c320             32 Free
 8     028f4d872ca8     7ffea05e9430        385,024 (byte3)
 9     02cfdf400008     7ffea051ec08             24 
10     02cfdf400020     7ffea046a318             40 
11     02cfdf400048     7ffea051ec08            150 
12     02cfdf4000e0     7ffea051ec08            122 
13     02cfdf400160     7ffea051ec08             42 
14     02cfdf400190     7ffea051ec08             30 
15     02cfdf4001b0     7ffea051ec08             50 
16     02cfdf4001e8     7ffea051ec08             38 
17     02cfdf400210     7ffea051ec08             26 
18     02cfdf400230     7ffea051ec08             34 
19     02cfdf400258     7ffea051ec08            118 
20     02cfdf4002d0     7ffea051ec08            126 
21     02cfdf400350     7ffea05e1838             32 
22     02cfdf400370     7ffea051ec08             30 
23     02cfdf400390     7ffea051ec08             32 
24     02cfdf4003b0     7ffea051ec08             84 
25     02cfdf400408     7ffea051ec08             98 
26     02cfdf400470     7ffea051ec08             48 
27     02cfdf4004a0     7ffea051ec08             54 
28     02cfdf4004d8     7ffea051ec08             70 
29     02cfdf400520     7ffea051ec08             70 
30     02cfdf400568     7ffea051ec08            112 
31     02cfdf4005d8     7ffea051ec08             38 
32     02cfdf400600     7ffea051ec08             90 
33     02cfdf400660     7ffea051ec08             42 
34     02cfdf400690     7ffea051ec08            104 
35     02cfdf4006f8     7ffea051ec08            130 
36     02cfdf400780     7ffea051ec08            160 
37     02cfdf400820     7ffea051ec08            124 
38     02cfdf4008a0     7ffea051ec08             38 
39     02cfdf4008c8     7ffea051ec08             24 
40     02cfdf4008e0     7ffea051ec08             44 
41     02cfdf400910     7ffea051ec08             24 
42     02cfdf400928     7ffea05e9430             24 
43     02cfdf400940     7ffea051ec08             26 
44     02cfdf400960     7ffea051ec08             42 
45     02cfdf400990     7ffea051ec08             40 
46     02cfdf4009b8     7ffea051ec08             32 
47     02cfdf4009d8     7ffea051ec08             32 
48     02cfdf4009f8     7ffea051ec08             40 
49     02cfdf400a20     7ffea051ec08             34 
50 
51 Statistics:
52           MT Count TotalSize Class Name
53 7ffea05e1838     1        32 System.Guid
54 7ffea046a318     1        40 System.RuntimeType
55 028f48d0c320     3        96 Free
56 7ffea051ec08    38     2,422 System.String
57 7ffea05e9430     4   855,096 System.Byte[]
58 Total 47 objects, 857,686 bytes

                    這個命令輸出的內容很多,當然,我們可以使用【!DumpHeap 028f4d800028 028f4d8d0ca8】命令,檢視我們關注的東西。

 1 0:000> !DumpHeap 028f4d800028     028f4d8d0ca8
 2          Address               MT           Size
 3     028f4d800028     028f48d0c320             32 Free
 4     028f4d800048     7ffea05e9430        185,024(byte1 變數) 
 5     028f4d82d308     028f48d0c320             32 Free
 6     028f4d82d328     7ffea05e9430        285,024 (byte2 變數)
 7     028f4d872c88     028f48d0c320             32 Free
 8     028f4d872ca8     7ffea05e9430        385,024 (byte3 變數)
 9 
10 Statistics:
11           MT Count TotalSize Class Name
12 028f48d0c320     3        96 Free
13 7ffea05e9430     3   855,072 System.Byte[]
14 Total 6 objects, 855,168 bytes

                    因為,我們定義的物件大小最小就是 185000,所以,我們可以使用【!DumpHeap -min 185000】命令檢視比 185000 大的物件。

 1 0:000> !DumpHeap -min 185000
 2          Address               MT           Size
 3     028f4d800048     7ffea05e9430        185,024 
 4     028f4d82d328     7ffea05e9430        285,024 
 5     028f4d872ca8     7ffea05e9430        385,024 
 6 
 7 Statistics:
 8           MT Count TotalSize Class Name
 9 7ffea05e9430     3   855,072 System.Byte[]
10 Total 3 objects, 855,072 bytes

                    我們【g】執行偵錯程式。控制檯應用程式輸出“2、GC 已經觸發,請檢視託管堆中的 byte2”字樣,偵錯程式自動中斷執行。
                    我們再次執行【!DumpHeap 028f4d800028 028f4d8d0ca8】命令,檢視 LOH 的變化。

 1 0:000> !DumpHeap 028f4d800028     028f4d8d0ca8
 2          Address               MT           Size
 3     028f4d800028     028f48d0c320             32 Free
 4     028f4d800048     7ffea05e9430        185,024 
 5     028f4d82d308     028f48d0c320        285,088 Free(byte2 變數已經釋放)
 6     028f4d872ca8     7ffea05e9430        385,024 
 7 
 8 Statistics:
 9           MT Count TotalSize Class Name
10 028f48d0c320     2   285,120 Free
11 7ffea05e9430     2   570,048 System.Byte[]
12 Total 4 objects, 855,168 bytes

                    我們再次執行【g】偵錯程式,控制檯程式輸出“3、已分配 byte4,檢視是否 Free 塊中”字樣,偵錯程式會自己中斷執行。
                    我們再次執行【!DumpHeap 028f4d800028 028f4d8d0ca8】命令,檢視 LOH 的變化。

 1 0:000> !DumpHeap 028f4d800028     028f4d8d0ca8
 2          Address               MT           Size
 3     028f4d800028     028f48d0c320             32 Free
 4     028f4d800048     7ffea05e9430        185,024 
 5     028f4d82d308     028f48d0c320             32 Free
 6     028f4d82d328     7ffea05e9430        280,024 (byte4變數)
 7     028f4d871900     028f48d0c320          5,032 Free(多餘出來的)
 8     028f4d872ca8     7ffea05e9430        385,024 
 9 
10 Statistics:
11           MT Count TotalSize Class Name
12 028f48d0c320     3     5,096 Free
13 7ffea05e9430     3   850,072 System.Byte[]
14 Total 6 objects, 855,168 bytes

                    紅色標註的已經說明了問題,分配的 byte4 物件大小正好在 Free 塊中,所以就把 byte4 直接儲存了。


        3.2.6、固定
            A、基礎知識
                垃圾收集器採用了一種緊縮技術來減少 GC 堆上的碎片。當 GC 要執行垃圾收集的操作時,空間碎片會進行合併形成一塊更大的可用空間,在託管堆上的活躍的物件也要發生移動,物件的地址會發生變化,針對該物件的所有引用都會被更新,所有活躍的物件相鄰存放,合併後的空間會緊跟在最後一個活躍物件的後邊。如果針對移動過的物件的所有引用都包含在 CLR 中,那是沒有問題的。

                如果 .NET 程式需要透過互用性服務(例如平臺呼叫或者COM 互用性)在 CLR 範圍之外工作,物件的移動就會產生問題。如果某個託管物件的引用被傳遞給一個底層的非託管 API ,那麼當非託管的 API 正在讀取或者寫入的記憶體同時移動了物件,就會導致嚴重的問題。
      
                我們舉例說明一下,先上一張圖,然後具體說明。
                

                在上圖中,在託管堆上起初包含了 5 個物件( A、B、C、D、E),物件 A 的地址 0x02000000,物件 C 的地址是 0x02000090。在某個特定時刻,透過平臺呼叫來呼叫了一個非同步的非託管的 API ,同時將物件 C 的地址(0x02000090)傳遞給 API。在呼叫這個非託管的非同步 API 的時候發生了一次垃圾回收的操作,使得物件 A 和物件 B 被回收。此時,託管堆上出現了空閒物件造成的縫隙,因而 垃圾收集器會透過緊縮託管堆來解決這個問題,因此,物件 C 移動到了地址 0x02000000 處,此地址以前是物件 A 的。此外,還合併了這兩塊空閒記憶體,並將它們放在堆的末尾。在完成了垃圾收集後,之前進行的非同步 API 呼叫決定寫入到最初傳遞給它的地址(0x02000090),因為當初儲存的是物件 C。此時,再看,已經是物是人非了,被寫入的記憶體不再由物件 C 佔用,就會導致系統產生問題,排查問題也挺麻煩的。

                如何解決在 GC 執行緊縮時仍能安全的呼叫非託管程式碼,有一種解決方案就是固定。是指將託管堆上的某個物件固定住,垃圾收集器回收記憶體時就不會移動該物件,直到解除對這個物件的固定為止。

                雖然透過固定物件的地址解決了在非託管程式碼呼叫期間的物件移動問題,但是也帶來了另一個問題,記憶體碎片化,因為記憶體不能緊縮併合並導致而成。如果太託管堆上存在大量的固定物件,那麼就會出現沒有足夠大而連續的空閒空間的情況,導致記憶體分配失敗。在舉例說明,如圖:
                

                如上圖所示,有數個空閒的小記憶體塊與活躍的物件交錯存放,如果執行一次垃圾收集,那麼託管堆的記憶體佈局將保持不變。由於活躍的物件都被固定住了,無法移動,垃圾收集器也就無法執行緊縮操作,又由於這些記憶體塊不是相鄰的,所以也不能合併。很容易導致記憶體分配失敗。

                LOH 中的碎片情況怎麼樣?
                    我們知道 LOH 採用“標記清除”,而不是緊縮的垃圾收集方式,這就意味著 LOH 上的物件永遠不會移動,那我們是否就可以不用管 LOH 物件,放心使用呢?不是的,如果沒有固定住 LOH 上的物件,也是一種很危險的假設。因為在在不同的 CLR 版本中,這種假設是會變化的,為了防止發生不可預測的問題,該固定的還是固定使用吧。
              
                我們可以使用【!GCHandles】顯示程序中的所有控制代碼。
            B、眼見為實
                除錯原始碼:ExampleCore_5_6
                除錯任務:如何固定物件和釋放物件
                1)、NTSD 除錯
                    編譯專案,開啟【Visual Studio 2022 Developer Command Prompt v17.9.6】命令列工具,輸入命令【NTSD E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_5_6\bin\Debug\net8.0\ExampleCore_5_6.exe】開啟偵錯程式。
                    進入偵錯程式,【g】直接執行程式,直到偵錯程式輸出如圖:
                    

                    此時,記憶體已經分配,還沒有執行垃圾回收,我們先看看當前程序中的所有控制代碼是否包含我們的陣列物件。
                    按組合鍵【ctrl+c】組合鍵,進入偵錯程式的中斷模式,直接執行【!GCHandles】命令,檢視所有的控制代碼資訊。

 1 0:002> !GCHandles
 2           Handle Type                  Object     Size             Data Type
 3 000001FA205A11B8 WeakShort   000001FA24C08FB8      400                  System.Diagnostics.Tracing.RuntimeEventSource
 4 000001FA205A11C0 WeakShort   000001FA24C09378      184                  System.Diagnostics.Tracing.NativeRuntimeEventSource
 5 000001FA205A11C8 WeakShort   000001FA24C09560       64                  System.Diagnostics.Tracing.EventSource+OverrideEventProvider
 6 000001FA205A11D0 WeakShort   000001FA24C094B0       64                  System.Diagnostics.Tracing.EventSource+OverrideEventProvider
 7 000001FA205A11D8 WeakShort   000001FA24C09298       64                  System.Diagnostics.Tracing.EventSource+OverrideEventProvider
 8 000001FA205A11E0 WeakShort   000001FA24C091E8       64                  System.Diagnostics.Tracing.EventSource+OverrideEventProvider
 9 000001FA205A1398 Strong      000001FA22402020     8184                  System.Object[]
10 000001FA205A13A0 Strong      000001FA24C092D8       64                  System.Diagnostics.Tracing.EventPipeEventProvider
11 000001FA205A13A8 Strong      000001FA24C095A0       64                  System.Diagnostics.Tracing.EventPipeEventProvider
12 000001FA205A13B0 Strong      000001FA24C094F0       88                  System.Diagnostics.Tracing.EtwEventProvider
13 000001FA205A13B8 Strong      000001FA24C09228       88                  System.Diagnostics.Tracing.EtwEventProvider
14 000001FA205A13C8 Strong      000001FA24C00188      128                  System.ExecutionEngineException
15 000001FA205A13D0 Strong      000001FA24C00108      128                  System.StackOverflowException
16 000001FA205A13D8 Strong      000001FA24C00088      128                  System.OutOfMemoryException
17 000001FA205A13E0 Strong      000001FA24C00028       96                  System.Int32[]
18 000001FA205A13E8 Strong      000001FA22400028     8184                  System.Object[]
19 000001FA205A15E0 Pinned      000001FA24C09E40      324                  System.SByte[](b3 變數,固定型別:Pinned20 000001FA205A15E8 Pinned      000001FA24C09D60      224                  System.SByte[](b2 變數,固定型別:Pinned21 000001FA205A15F0 Pinned      000001FA24C09CE0      124                  System.SByte[](b1 變數,固定型別:Pinned22 000001FA205A15F8 Pinned      000001FA24C00208       24                  System.Object
23 
24 Statistics:
25               MT    Count    TotalSize Class Name
26 00007ffca9d25fa8        1           24 System.Object
27 00007ffca9dd9df8        1           96 System.Int32[]
28 00007ffca9ea37b8        2          128 System.Diagnostics.Tracing.EventPipeEventProvider
29 00007ffca9e70c90        1          128 System.ExecutionEngineException
30 00007ffca9e70b90        1          128 System.StackOverflowException
31 00007ffca9e70a90        1          128 System.OutOfMemoryException
32 00007ffca9ea3a20        2          176 System.Diagnostics.Tracing.EtwEventProvider
33 00007ffca9ea85d0        1          184 System.Diagnostics.Tracing.NativeRuntimeEventSource
34 00007ffca9ea2b28        4          256 System.Diagnostics.Tracing.EventSource+OverrideEventProvider
35 00007ffca9ea2208        1          400 System.Diagnostics.Tracing.RuntimeEventSource
36 00007ffca9eaab40        3          672 System.SByte[](這就是包含 3 個位元組陣列的型別)
37 00007ffca9d2c4d8        2        16368 System.Object[]
38 Total 20 objects
39 
40 Handles:
41     Strong Handles:       10
42     Pinned Handles:       4(3個型別是我們定義的)
43     Weak Short Handles:   6

                    我們能看到 10 個 Strong 型別的控制代碼,4 個 Pinned 型別的控制代碼(包含我們定義的 3 個)和 6 個 Weak Short 型別的控制代碼。
                    我們切換到託管執行緒上下文中去看看,切換執行緒【~0s】。

1 0:002> ~0s
2 ntdll!NtDeviceIoControlFile+0x14:
3 00007ffd`cf30d0c4 c3              ret

                    我們使用【!clrstack -a】命令檢視一下託管呼叫棧。

 1 0:000> !clrstack -a
 2 OS Thread Id: 0x1d6c (0)
 3         Child SP               IP Call Site
 4 000000851CDAE548 00007ffdcf30d0c4 [InlinedCallFrame: 000000851cdae548]
 5 000000851CDAE548 00007ffdba07787a [InlinedCallFrame: 000000851cdae548]
 6 。。。。。。(省略了)
 7 000000851CDAE6D0 00007FFCA9DF1B3E ExampleCore_5_6.Program.Run()
 8     PARAMETERS:
 9         this (0x000000851CDAE7A0) = 0x000001fa24c09628
10     LOCALS:
11         0x000000851CDAE788 = 0x000001fa24c09ce0(b1 變數)
12         0x000000851CDAE780 = 0x000001fa24c09d60(b2 變數)
13         0x000000851CDAE778 = 0x000001fa24c09e40(b3 變數)
14         0x000000851CDAE770 = 0x000001fa205a15f1(控制代碼變數 h1)
15         0x000000851CDAE768 = 0x000001fa205a15e9(控制代碼變數 h2)
16         0x000000851CDAE760 = 0x000001fa205a15e1(控制代碼變數 h3)
17 
18 000000851CDAE7A0 00007FFCA9DF1988 ExampleCore_5_6.Program.Main(System.String[])
19     PARAMETERS:
20         args (0x000000851CDAE7F0) = 0x000001fa24c08e90
21     LOCALS:
22         0x000000851CDAE7D8 = 0x000001fa24c09628

                    紅色標註的就是位元組陣列的變數(b1,b2,b3),藍色的就是控制代碼變數(h1,h2,h3),都分配了空間。
                    我們可以使用【!do】或者【!DumpObj】命令檢視一下陣列變數。

 1 0:000> !do 0x000001fa24c09ce0
 2 Name:        System.SByte[]
 3 MethodTable: 00007ffca9eaab40
 4 EEClass:     00007ffca9eaaac0
 5 Tracked Type: false
 6 Size:        124(0x7c) bytes
 7 Array:       Rank 1, Number of elements 100, Type SByte
 8 Content:     ....................................................................................................
 9 Fields:
10 None

                    【g】繼續執行偵錯程式,直到偵錯程式輸出“Press any key to Free!”,說明完成了第一次垃圾回收。如圖:
                    

                    按組合鍵【ctrl+c】進入中斷模式,繼續執行【!GCHandles】檢視控制代碼詳情。

 1 0:002> !GCHandles
 2           Handle Type                  Object     Size             Data Type
 3 000001FA205A11B8 WeakShort   000001FA24C08FB8      400                  System.Diagnostics.Tracing.RuntimeEventSource
 4 000001FA205A11C0 WeakShort   000001FA24C09378      184                  System.Diagnostics.Tracing.NativeRuntimeEventSource
 5 000001FA205A11C8 WeakShort   000001FA24C09560       64                  System.Diagnostics.Tracing.EventSource+OverrideEventProvider
 6 000001FA205A11D0 WeakShort   000001FA24C094B0       64                  System.Diagnostics.Tracing.EventSource+OverrideEventProvider
 7 000001FA205A11D8 WeakShort   000001FA24C09298       64                  System.Diagnostics.Tracing.EventSource+OverrideEventProvider
 8 000001FA205A11E0 WeakShort   000001FA24C091E8       64                  System.Diagnostics.Tracing.EventSource+OverrideEventProvider
 9 000001FA205A1398 Strong      000001FA22402020     8184                  System.Object[]
10 000001FA205A13A0 Strong      000001FA24C092D8       64                  System.Diagnostics.Tracing.EventPipeEventProvider
11 000001FA205A13A8 Strong      000001FA24C095A0       64                  System.Diagnostics.Tracing.EventPipeEventProvider
12 000001FA205A13B0 Strong      000001FA24C094F0       88                  System.Diagnostics.Tracing.EtwEventProvider
13 000001FA205A13B8 Strong      000001FA24C09228       88                  System.Diagnostics.Tracing.EtwEventProvider
14 000001FA205A13C8 Strong      000001FA24C00188      128                  System.ExecutionEngineException
15 000001FA205A13D0 Strong      000001FA24C00108      128                  System.StackOverflowException
16 000001FA205A13D8 Strong      000001FA24C00088      128                  System.OutOfMemoryException
17 000001FA205A13E0 Strong      000001FA24C00028       96                  System.Int32[]
18 000001FA205A13E8 Strong      000001FA22400028     8184                  System.Object[]
19 000001FA205A15E0 Pinned      000001FA24C09E40      324                  System.SByte[]
20 000001FA205A15E8 Pinned      000001FA24C09D60      224                  System.SByte[]
21 000001FA205A15F0 Pinned      000001FA24C09CE0      124                  System.SByte[]
22 000001FA205A15F8 Pinned      000001FA24C00208       24                  System.Object
23 
24 Statistics:
25               MT    Count    TotalSize Class Name
26 00007ffca9d25fa8        1           24 System.Object
27 00007ffca9dd9df8        1           96 System.Int32[]
28 00007ffca9ea37b8        2          128 System.Diagnostics.Tracing.EventPipeEventProvider
29 00007ffca9e70c90        1          128 System.ExecutionEngineException
30 00007ffca9e70b90        1          128 System.StackOverflowException
31 00007ffca9e70a90        1          128 System.OutOfMemoryException
32 00007ffca9ea3a20        2          176 System.Diagnostics.Tracing.EtwEventProvider
33 00007ffca9ea85d0        1          184 System.Diagnostics.Tracing.NativeRuntimeEventSource
34 00007ffca9ea2b28        4          256 System.Diagnostics.Tracing.EventSource+OverrideEventProvider
35 00007ffca9ea2208        1          400 System.Diagnostics.Tracing.RuntimeEventSource
36 00007ffca9eaab40        3          672 System.SByte[]
37 00007ffca9d2c4d8        2        16368 System.Object[]
38 Total 20 objects
39 
40 Handles:
41     Strong Handles:       10
42     Pinned Handles:       4
43     Weak Short Handles:   6

                    沒有任何變化,垃圾也沒回收控制代碼和陣列的記憶體。
                    我們繼續執行【g】,直到偵錯程式輸出“Press any key to GC2!”,此時已經完成釋放控制代碼,按組合鍵【ctrl+c】進入中斷模式,我們可以使用【!GCHandles】命令確認一下。

 1 0:002> !GCHandles
 2           Handle Type                  Object     Size             Data Type
 3 000001FA205A11B8 WeakShort   000001FA24C08FB8      400                  System.Diagnostics.Tracing.RuntimeEventSource
 4 000001FA205A11C0 WeakShort   000001FA24C09378      184                  System.Diagnostics.Tracing.NativeRuntimeEventSource
 5 000001FA205A11C8 WeakShort   000001FA24C09560       64                  System.Diagnostics.Tracing.EventSource+OverrideEventProvider
 6 000001FA205A11D0 WeakShort   000001FA24C094B0       64                  System.Diagnostics.Tracing.EventSource+OverrideEventProvider
 7 000001FA205A11D8 WeakShort   000001FA24C09298       64                  System.Diagnostics.Tracing.EventSource+OverrideEventProvider
 8 000001FA205A11E0 WeakShort   000001FA24C091E8       64                  System.Diagnostics.Tracing.EventSource+OverrideEventProvider
 9 000001FA205A1398 Strong      000001FA22402020     8184                  System.Object[]
10 000001FA205A13A0 Strong      000001FA24C092D8       64                  System.Diagnostics.Tracing.EventPipeEventProvider
11 000001FA205A13A8 Strong      000001FA24C095A0       64                  System.Diagnostics.Tracing.EventPipeEventProvider
12 000001FA205A13B0 Strong      000001FA24C094F0       88                  System.Diagnostics.Tracing.EtwEventProvider
13 000001FA205A13B8 Strong      000001FA24C09228       88                  System.Diagnostics.Tracing.EtwEventProvider
14 000001FA205A13C8 Strong      000001FA24C00188      128                  System.ExecutionEngineException
15 000001FA205A13D0 Strong      000001FA24C00108      128                  System.StackOverflowException
16 000001FA205A13D8 Strong      000001FA24C00088      128                  System.OutOfMemoryException
17 000001FA205A13E0 Strong      000001FA24C00028       96                  System.Int32[]
18 000001FA205A13E8 Strong      000001FA22400028     8184                  System.Object[]
19 000001FA205A15F8 Pinned      000001FA24C00208       24                  System.Object
20 
21 Statistics:
22               MT    Count    TotalSize Class Name
23 00007ffca9d25fa8        1           24 System.Object
24 00007ffca9dd9df8        1           96 System.Int32[]
25 00007ffca9ea37b8        2          128 System.Diagnostics.Tracing.EventPipeEventProvider
26 00007ffca9e70c90        1          128 System.ExecutionEngineException
27 00007ffca9e70b90        1          128 System.StackOverflowException
28 00007ffca9e70a90        1          128 System.OutOfMemoryException
29 00007ffca9ea3a20        2          176 System.Diagnostics.Tracing.EtwEventProvider
30 00007ffca9ea85d0        1          184 System.Diagnostics.Tracing.NativeRuntimeEventSource
31 00007ffca9ea2b28        4          256 System.Diagnostics.Tracing.EventSource+OverrideEventProvider
32 00007ffca9ea2208        1          400 System.Diagnostics.Tracing.RuntimeEventSource
33 00007ffca9d2c4d8        2        16368 System.Object[]
34 Total 17 objects
35 
36 Handles:
37     Strong Handles:       10
38     Pinned Handles:       1(以前是 4 個,現在是 1 個,少了 3 個)
39     Weak Short Handles:   6

                    我們可以切換到託管執行緒上下文中檢視 Run 方法的棧幀來確定。

1 0:002> ~0s
2 ntdll!NtDeviceIoControlFile+0x14:
3 00007ffd`cf30d0c4 c3              ret

                    使用【!clrstack -a】檢視 Run 方法的棧幀。

 1 0:000> !clrstack -a
 2 OS Thread Id: 0x1d6c (0)
 3         Child SP               IP Call Site
 4 000000851CDAE578 00007ffdcf30d0c4 [InlinedCallFrame: 000000851cdae578] Interop+Kernel32.<ReadConsoleInput>g____PInvoke|45_0(IntPtr, INPUT_RECORD*, Int32, Int32*)
 5 000000851CDAE578 00007ffca9df1d05 [InlinedCallFrame: 000000851cdae578] Interop+Kernel32.<ReadConsoleInput>g____PInvoke|45_0(IntPtr, INPUT_RECORD*, Int32, Int32*)
 6 。。。。。。(省略了)
 7 000000851CDAE6D0 00007FFCA9DF1BA4 ExampleCore_5_6.Program.Run()
 8     PARAMETERS:
 9         this (0x000000851CDAE7A0) = 0x000001fa24c09628
10     LOCALS:
11         0x000000851CDAE788 = 0x000001fa24c09ce0
12         0x000000851CDAE780 = 0x000001fa24c09d60
13         0x000000851CDAE778 = 0x000001fa24c09e40
14         0x000000851CDAE770 = 0x0000000000000000(區域性變數 h1 已經釋放)
15         0x000000851CDAE768 = 0x0000000000000000(區域性變數 h2 已經釋放)
16         0x000000851CDAE760 = 0x0000000000000000(區域性變數 h3 已經釋放)
17 
18 000000851CDAE7A0 00007FFCA9DF1988 ExampleCore_5_6.Program.Main(System.String[])
19     PARAMETERS:
20         args (0x000000851CDAE7F0) = 0x000001fa24c08e90
21     LOCALS:
22         0x000000851CDAE7D8 = 0x000001fa24c09628

                    紅色標註的說明控制代碼變數已經釋放,陣列變數還沒有回收。
                    【g】繼續執行,直到偵錯程式輸出“Press any key to Exit!”,此時,第二次垃圾回收已經完成,剩下的陣列變數也應該被回收了。按組合鍵【ctrl+c】進入中斷模式,我們直接切換執行緒,執行【!clrstack -a】命令就可以了。

1 0:002> ~0s
2 ntdll!NtDeviceIoControlFile+0x14:
3 00007ffd`cf30d0c4 c3              ret

                    執行【!clrstack -a】命令。

 1 0:000> !clrstack -a
 2 OS Thread Id: 0x1d6c (0)
 3         Child SP               IP Call Site
 4 000000851CDAE648 00007ffdcf30d0c4 [InlinedCallFrame: 000000851cdae648] Interop+Kernel32.<ReadConsoleInput>g____PInvoke|45_0(IntPtr, INPUT_RECORD*, Int32, Int32*)
 5 000000851CDAE648 00007ffca9df1e25 [InlinedCallFrame: 000000851cdae648] Interop+Kernel32.<ReadConsoleInput>g____PInvoke|45_0(IntPtr, INPUT_RECORD*, Int32, Int32*)
 6 。。。。。。(省略了)
 7 000000851CDAE7A0 00007FFCA9DF19A4 ExampleCore_5_6.Program.Main(System.String[])
 8     PARAMETERS:
 9         args (0x000000851CDAE7F0) = 0x000001fa24c08e90
10     LOCALS:
11         0x000000851CDAE7D8 = 0x000001fa24c09628

                    從輸出中就可以看到,我們並沒有看到 Run 方法的棧幀,只有 ExampleCore_5_6.Program.Main 棧幀了,說明已經回收了,驗證了我們的說法。


                2)、Windbg Preview 除錯
                    編譯專案,開啟【Windbg Preview】偵錯程式,依次點選【檔案】---【Launch executable】,載入我們的專案檔案:ExampleCore_5_6.exe,進入到偵錯程式。
                    進入到偵錯程式,我們可以直接輸入【g】命令,繼續執行偵錯程式,我們的控制檯會輸出“Press any key to Alloc And Pinned!”,如果此時,我們查詢變數是一無所獲的,因為還沒有分配記憶體。我們繼續在控制檯程式中按任意鍵繼續執行程式,直到程式輸出“Press any key to GC1!”,效果如圖:
                    

                    此時,記憶體已經分配,我們回到偵錯程式,點選【break】按鈕,中斷偵錯程式的執行,開始我們的除錯。
                    我們直接輸入【!GCHandles】命令,檢視當前程序中所有的控制代碼。

 1 0:006> !GCHandles
 2           Handle Type                  Object     Size             Data Type
 3 00000197A1C311B8 WeakShort   00000197a6008fb8      400                  System.Diagnostics.Tracing.RuntimeEventSource
 4 00000197A1C311C0 WeakShort   00000197a6009378      184                  System.Diagnostics.Tracing.NativeRuntimeEventSource
 5 00000197A1C311C8 WeakShort   00000197a6009560       64                  System.Diagnostics.Tracing.EventSource+OverrideEventProvider
 6 00000197A1C311D0 WeakShort   00000197a60094b0       64                  System.Diagnostics.Tracing.EventSource+OverrideEventProvider
 7 00000197A1C311D8 WeakShort   00000197a6009298       64                  System.Diagnostics.Tracing.EventSource+OverrideEventProvider
 8 00000197A1C311E0 WeakShort   00000197a60091e8       64                  System.Diagnostics.Tracing.EventSource+OverrideEventProvider
 9 00000197A1C31398 Strong      00000197a3802020     8184                  System.Object[]
10 00000197A1C313A0 Strong      00000197a60092d8       64                  System.Diagnostics.Tracing.EventPipeEventProvider
11 00000197A1C313A8 Strong      00000197a60095a0       64                  System.Diagnostics.Tracing.EventPipeEventProvider
12 00000197A1C313B0 Strong      00000197a60094f0       88                  System.Diagnostics.Tracing.EtwEventProvider
13 00000197A1C313B8 Strong      00000197a6009228       88                  System.Diagnostics.Tracing.EtwEventProvider
14 00000197A1C313C8 Strong      00000197a6000188      128                  System.ExecutionEngineException
15 00000197A1C313D0 Strong      00000197a6000108      128                  System.StackOverflowException
16 00000197A1C313D8 Strong      00000197a6000088      128                  System.OutOfMemoryException
17 00000197A1C313E0 Strong      00000197a6000028       96                  System.Int32[]
18 00000197A1C313E8 Strong      00000197a3800028     8184                  System.Object[]
19 00000197A1C315E0 Pinned      00000197a6009e40      324                  System.SByte[](Run()方法中的變數 b3,固定型別:Pinned)
20 00000197A1C315E8 Pinned      00000197a6009d60      224                  System.SByte[](Run()方法中的變數 b2,固定型別:Pinned)
21 00000197A1C315F0 Pinned      00000197a6009ce0      124                  System.SByte[](Run()方法中的變數 b1,固定型別:Pinned)
22 00000197A1C315F8 Pinned      00000197a6000208       24                  System.Object
23 
24 Statistics:
25               MT    Count    TotalSize Class Name
26 00007ffc8f275fa8        1           24 System.Object
27 00007ffc8f329df8        1           96 System.Int32[]
28 00007ffc8f3f37b8        2          128 System.Diagnostics.Tracing.EventPipeEventProvider
29 00007ffc8f3c0c90        1          128 System.ExecutionEngineException
30 00007ffc8f3c0b90        1          128 System.StackOverflowException
31 00007ffc8f3c0a90        1          128 System.OutOfMemoryException
32 00007ffc8f3f3a20        2          176 System.Diagnostics.Tracing.EtwEventProvider
33 00007ffc8f3f85d0        1          184 System.Diagnostics.Tracing.NativeRuntimeEventSource
34 00007ffc8f3f2b28        4          256 System.Diagnostics.Tracing.EventSource+OverrideEventProvider
35 00007ffc8f3f2208        1          400 System.Diagnostics.Tracing.RuntimeEventSource
36 00007ffc8f3fab40        3          672 System.SByte[](這裡也是我們 3 個變數的統計資訊,672就是 124+224+324 的和)
37 00007ffc8f27c4d8        2        16368 System.Object[]
38 Total 20 objects
39 
40 Handles:
41     Strong Handles:       10
42     Pinned Handles:       4
43     Weak Short Handles:   6

                    我們看到有 10 個 Strong 型別的控制代碼,4 個 Pinned 型別的控制代碼,6 個 Weak Short 型別的控制代碼。紅色標註的就是我們宣告的 b1,b2,b3 3個變數。
                    我們切換到託管線上的上下文中,去執行緒呼叫棧中看看能不能找到 b1、b2 和 b3 這三個變數。

1 0:006> ~0s
2 ntdll!NtDeviceIoControlFile+0x14:
3 00007ffd`cf30d0c4 c3              ret

                    繼續使用【!ClrStack -a】命令顯示執行緒呼叫棧的所有引數和區域性變數。

 1 0:000> !clrstack -a
 2 OS Thread Id: 0x598 (0)
 3         Child SP               IP Call Site
 4 00000087DC9CE3C8 00007ffdcf30d0c4 [InlinedCallFrame: 00000087dc9ce3c8] 
 5 00000087DC9CE3C8 00007ffd49df787a [InlinedCallFrame: 00000087dc9ce3c8] 
 6 00000087DC9CE3A0 00007ffd49df787a Interop+Kernel32.ReadConsoleInput(IntPtr, INPUT_RECORD ByRef, Int32, Int32 ByRef) [/_/src/libraries/System.Console/src/Microsoft.Interop.LibraryImportGenerator/Microsoft.Interop.LibraryImportGenerator/LibraryImports.g.cs @ 470]
 7     PARAMETERS:
 8         hConsoleInput = <no data>
 9         buffer = <no data>
10         numInputRecords_UseOne = <no data>
11         numEventsRead = <no data>
12     LOCALS:
13         <no data>
14         <no data>
15         <no data>
16         <no data>
17         <no data>
18         <no data>
19         <no data>
20 
21 00000087DC9CE490 00007ffd49dfaa0a System.ConsolePal.ReadKey(Boolean) [/_/src/libraries/System.Console/src/System/ConsolePal.Windows.cs @ 334]
22     PARAMETERS:
23         intercept (<CLR reg>) = 0x0000000000000000
24     LOCALS:
25         <no data>
26         <no data>
27         <no data>
28         <no data>
29         <no data>
30         <no data>
31         <no data>
32         0x00000087DC9CE4D0 = 0x00000197a6009c98
33         <no data>
34         <no data>
35         <no data>
36         <no data>
37         <no data>
38 
39 00000087DC9CE550 00007ffc8f341b3e ExampleCore_5_6.Program.Run() [E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_5_6\Program.cs @ 34]
40     PARAMETERS:
41         this (0x00000087DC9CE620) = 0x00000197a6009628
42     LOCALS:
43         0x00000087DC9CE608 = 0x00000197a6009ce0(這就是 b1 變數)
44         0x00000087DC9CE600 = 0x00000197a6009d60(這就是 b2 變數)
45         0x00000087DC9CE5F8 = 0x00000197a6009e40(這就是 b3 變數)
46         0x00000087DC9CE5F0 = 0x00000197a1c315f1
47         0x00000087DC9CE5E8 = 0x00000197a1c315e9
48         0x00000087DC9CE5E0 = 0x00000197a1c315e1
49 
50 00000087DC9CE620 00007ffc8f341988 ExampleCore_5_6.Program.Main(System.String[]) [E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_5_6\Program.cs @ 10]
51     PARAMETERS:
52         args (0x00000087DC9CE670) = 0x00000197a6008e90
53     LOCALS:
54         0x00000087DC9CE658 = 0x00000197a6009628

                    我們主要關注 Run 方法的呼叫棧,標紅的就是我們宣告的 3 個區域性變數,我們可以使用【!do】命令依次檢視。

 1 0:000> !do 0x00000197a6009ce0
 2 Name:        System.SByte[]
 3 MethodTable: 00007ffc8f3fab40
 4 EEClass:     00007ffc8f3faac0
 5 Tracked Type: false
 6 Size:        124(0x7c) bytes
 7 Array:       Rank 1, Number of elements 100, Type SByte (Print Array)
 8 Content:     ....................................................................................................
 9 Fields:
10 None
11 
12 0:000> !do 00000197a6009d60
13 Name:        System.SByte[]
14 MethodTable: 00007ffc8f3fab40
15 EEClass:     00007ffc8f3faac0
16 Tracked Type: false
17 Size:        224(0xe0) bytes
18 Array:       Rank 1, Number of elements 200, Type SByte (Print Array)
19 Content:     ................................................................................................................................
20 Fields:
21 None
22 
23 0:000> !do 0x00000197a6009e40
24 Name:        System.SByte[]
25 MethodTable: 00007ffc8f3fab40
26 EEClass:     00007ffc8f3faac0
27 Tracked Type: false
28 Size:        324(0x144) bytes
29 Array:       Rank 1, Number of elements 300, Type SByte (Print Array)
30 Content:     ................................................................................................................................
31 Fields:
32 None

                    我們繼續【g】執行偵錯程式,控制檯程式輸出“Press any key to Free!”,回收記憶體,但是還沒有取消固定,回到偵錯程式,點選【break】按鈕,中斷偵錯程式的執行。
                    我們先執行【!GCHandles】命令,看看控制代碼還在嗎?

 1 0:001> !GCHandles
 2           Handle Type                  Object     Size             Data Type
 3 00000197A1C311B8 WeakShort   00000197a6008fb8      400                  System.Diagnostics.Tracing.RuntimeEventSource
 4 00000197A1C311C0 WeakShort   00000197a6009378      184                  System.Diagnostics.Tracing.NativeRuntimeEventSource
 5 00000197A1C311C8 WeakShort   00000197a6009560       64                  System.Diagnostics.Tracing.EventSource+OverrideEventProvider
 6 00000197A1C311D0 WeakShort   00000197a60094b0       64                  System.Diagnostics.Tracing.EventSource+OverrideEventProvider
 7 00000197A1C311D8 WeakShort   00000197a6009298       64                  System.Diagnostics.Tracing.EventSource+OverrideEventProvider
 8 00000197A1C311E0 WeakShort   00000197a60091e8       64                  System.Diagnostics.Tracing.EventSource+OverrideEventProvider
 9 00000197A1C31398 Strong      00000197a3802020     8184                  System.Object[]
10 00000197A1C313A0 Strong      00000197a60092d8       64                  System.Diagnostics.Tracing.EventPipeEventProvider
11 00000197A1C313A8 Strong      00000197a60095a0       64                  System.Diagnostics.Tracing.EventPipeEventProvider
12 00000197A1C313B0 Strong      00000197a60094f0       88                  System.Diagnostics.Tracing.EtwEventProvider
13 00000197A1C313B8 Strong      00000197a6009228       88                  System.Diagnostics.Tracing.EtwEventProvider
14 00000197A1C313C8 Strong      00000197a6000188      128                  System.ExecutionEngineException
15 00000197A1C313D0 Strong      00000197a6000108      128                  System.StackOverflowException
16 00000197A1C313D8 Strong      00000197a6000088      128                  System.OutOfMemoryException
17 00000197A1C313E0 Strong      00000197a6000028       96                  System.Int32[]
18 00000197A1C313E8 Strong      00000197a3800028     8184                  System.Object[]
19 00000197A1C315E0 Pinned      00000197a6009e40      324                  System.SByte[]
20 00000197A1C315E8 Pinned      00000197a6009d60      224                  System.SByte[]
21 00000197A1C315F0 Pinned      00000197a6009ce0      124                  System.SByte[]
22 00000197A1C315F8 Pinned      00000197a6000208       24                  System.Object
23 
24 Statistics:
25               MT    Count    TotalSize Class Name
26 00007ffc8f275fa8        1           24 System.Object
27 00007ffc8f329df8        1           96 System.Int32[]
28 00007ffc8f3f37b8        2          128 System.Diagnostics.Tracing.EventPipeEventProvider
29 00007ffc8f3c0c90        1          128 System.ExecutionEngineException
30 00007ffc8f3c0b90        1          128 System.StackOverflowException
31 00007ffc8f3c0a90        1          128 System.OutOfMemoryException
32 00007ffc8f3f3a20        2          176 System.Diagnostics.Tracing.EtwEventProvider
33 00007ffc8f3f85d0        1          184 System.Diagnostics.Tracing.NativeRuntimeEventSource
34 00007ffc8f3f2b28        4          256 System.Diagnostics.Tracing.EventSource+OverrideEventProvider
35 00007ffc8f3f2208        1          400 System.Diagnostics.Tracing.RuntimeEventSource
36 00007ffc8f3fab40        3          672 System.SByte[]
37 00007ffc8f27c4d8        2        16368 System.Object[]
38 Total 20 objects
39 
40 Handles:
41     Strong Handles:       10
42     Pinned Handles:       4
43     Weak Short Handles:   6

                    控制代碼還在,這是正確的,我們還沒有釋放控制代碼。我們再次切換到託管執行緒上下文,看看有沒有釋放記憶體(已經執行垃圾回收了)。

 1 0:001> ~0s
 2 ntdll!NtDeviceIoControlFile+0x14:
 3 00007ffd`cf30d0c4 c3              ret
 4 
 5 0:000> !ClrStack -a
 6 OS Thread Id: 0x598 (0)
 7         Child SP               IP Call Site
 8 00000087DC9CE3F8 00007ffdcf30d0c4 [InlinedCallFrame: 00000087dc9ce3f8] Interop+Kernel32.g____PInvoke|45_0(IntPtr, INPUT_RECORD*, Int32, Int32*)
 9 00000087DC9CE3F8 00007ffc8f341d05 [InlinedCallFrame: 00000087dc9ce3f8] Interop+Kernel32.g____PInvoke|45_0(IntPtr, INPUT_RECORD*, Int32, Int32*)
10 。。。。。。(省略了)
11 00000087DC9CE550 00007ffc8f341b64 ExampleCore_5_6.Program.Run() [E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_5_6\Program.cs @ 39]
12     PARAMETERS:
13         this (0x00000087DC9CE620) = 0x00000197a6009628
14     LOCALS:
15         0x00000087DC9CE608 = 0x00000197a6009ce0
16         0x00000087DC9CE600 = 0x00000197a6009d60
17         0x00000087DC9CE5F8 = 0x00000197a6009e40
18         0x00000087DC9CE5F0 = 0x00000197a1c315f1
19         0x00000087DC9CE5E8 = 0x00000197a1c315e9
20         0x00000087DC9CE5E0 = 0x00000197a1c315e1
21 
22 00000087DC9CE620 00007ffc8f341988 ExampleCore_5_6.Program.Main(System.String[]) [E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_5_6\Program.cs @ 10]
23     PARAMETERS:
24         args (0x00000087DC9CE670) = 0x00000197a6008e90
25     LOCALS:
26         0x00000087DC9CE658 = 0x00000197a6009628
27 
28 
29 0:000> !DumpObj /d 00000197a6009ce0
30 Name:        System.SByte[]
31 MethodTable: 00007ffc8f3fab40
32 EEClass:     00007ffc8f3faac0
33 Tracked Type: false
34 Size:        124(0x7c) bytes
35 Array:       Rank 1, Number of elements 100, Type SByte (Print Array)
36 Content:     ....................................................................................................
37 Fields:
38 None

                  我們從輸出結果中可以看到,物件依然存在,因為該物件被固定住了。
                  我們繼續執行【g】,控制檯程式輸出“Press any key to GC2!”,說明已經釋放控制代碼,將要執行第二次垃圾回收。回到偵錯程式,點選【break】按鈕,中斷偵錯程式的執行,開始除錯。
                  繼續執行【!GCHandles】命令,檢視控制代碼的情況。

 1 0:001> !GCHandles
 2           Handle Type                  Object     Size             Data Type
 3 00000197A1C311B8 WeakShort   00000197a6008fb8      400                  System.Diagnostics.Tracing.RuntimeEventSource
 4 00000197A1C311C0 WeakShort   00000197a6009378      184                  System.Diagnostics.Tracing.NativeRuntimeEventSource
 5 00000197A1C311C8 WeakShort   00000197a6009560       64                  System.Diagnostics.Tracing.EventSource+OverrideEventProvider
 6 00000197A1C311D0 WeakShort   00000197a60094b0       64                  System.Diagnostics.Tracing.EventSource+OverrideEventProvider
 7 00000197A1C311D8 WeakShort   00000197a6009298       64                  System.Diagnostics.Tracing.EventSource+OverrideEventProvider
 8 00000197A1C311E0 WeakShort   00000197a60091e8       64                  System.Diagnostics.Tracing.EventSource+OverrideEventProvider
 9 00000197A1C31398 Strong      00000197a3802020     8184                  System.Object[]
10 00000197A1C313A0 Strong      00000197a60092d8       64                  System.Diagnostics.Tracing.EventPipeEventProvider
11 00000197A1C313A8 Strong      00000197a60095a0       64                  System.Diagnostics.Tracing.EventPipeEventProvider
12 00000197A1C313B0 Strong      00000197a60094f0       88                  System.Diagnostics.Tracing.EtwEventProvider
13 00000197A1C313B8 Strong      00000197a6009228       88                  System.Diagnostics.Tracing.EtwEventProvider
14 00000197A1C313C8 Strong      00000197a6000188      128                  System.ExecutionEngineException
15 00000197A1C313D0 Strong      00000197a6000108      128                  System.StackOverflowException
16 00000197A1C313D8 Strong      00000197a6000088      128                  System.OutOfMemoryException
17 00000197A1C313E0 Strong      00000197a6000028       96                  System.Int32[]
18 00000197A1C313E8 Strong      00000197a3800028     8184                  System.Object[]
19 00000197A1C315F8 Pinned      00000197a6000208       24                  System.Object
20 
21 Statistics:
22               MT    Count    TotalSize Class Name
23 00007ffc8f275fa8        1           24 System.Object
24 00007ffc8f329df8        1           96 System.Int32[]
25 00007ffc8f3f37b8        2          128 System.Diagnostics.Tracing.EventPipeEventProvider
26 00007ffc8f3c0c90        1          128 System.ExecutionEngineException
27 00007ffc8f3c0b90        1          128 System.StackOverflowException
28 00007ffc8f3c0a90        1          128 System.OutOfMemoryException
29 00007ffc8f3f3a20        2          176 System.Diagnostics.Tracing.EtwEventProvider
30 00007ffc8f3f85d0        1          184 System.Diagnostics.Tracing.NativeRuntimeEventSource
31 00007ffc8f3f2b28        4          256 System.Diagnostics.Tracing.EventSource+OverrideEventProvider
32 00007ffc8f3f2208        1          400 System.Diagnostics.Tracing.RuntimeEventSource
33 00007ffc8f27c4d8        2        16368 System.Object[]
34 Total 17 objects
35 
36 Handles:
37     Strong Handles:       10
38     Pinned Handles:       1
39     Weak Short Handles:   6

                  真沒有了,Pinned 型別的控制代碼以前是 4 個,現在是 1 個,少了 3 個。切換到託管執行緒上下文,執行命令【!clrstack -a】看看呼叫棧,也發生了變化。

 1 0:001> ~0s
 2 ntdll!NtDeviceIoControlFile+0x14:
 3 00007ffd`cf30d0c4 c3              ret
 4 
 5 0:000> !clrstack -a
 6 OS Thread Id: 0x598 (0)
 7         Child SP               IP Call Site
 8 00000087DC9CE3F8 00007ffdcf30d0c4 [InlinedCallFrame: 00000087dc9ce3f8] Interop+Kernel32.g____PInvoke|45_0(IntPtr, INPUT_RECORD*, Int32, Int32*)
 9 00000087DC9CE3F8 00007ffc8f341d05 [InlinedCallFrame: 00000087dc9ce3f8] Interop+Kernel32.g____PInvoke|45_0(IntPtr, INPUT_RECORD*, Int32, Int32*)
10 。。。。。。(省略了)
11 00000087DC9CE550 00007ffc8f341ba4 ExampleCore_5_6.Program.Run() [E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_5_6\Program.cs @ 46]
12     PARAMETERS:
13         this (0x00000087DC9CE620) = 0x00000197a6009628
14     LOCALS:
15         0x00000087DC9CE608 = 0x00000197a6009ce0(此時,b1 還在託管堆上)
16         0x00000087DC9CE600 = 0x00000197a6009d60(此時,b2 還在託管堆上)
17         0x00000087DC9CE5F8 = 0x00000197a6009e40(此時,b3 還在託管堆上)
18         0x00000087DC9CE5F0 = 0x0000000000000000(控制代碼 h1已經釋放了,這是變化的)
19         0x00000087DC9CE5E8 = 0x0000000000000000(控制代碼 h2已經釋放了,這是變化的)
20         0x00000087DC9CE5E0 = 0x0000000000000000(控制代碼 h3已經釋放了,這是變化的)
21 
22 00000087DC9CE620 00007ffc8f341988 ExampleCore_5_6.Program.Main(System.String[]) [E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_5_6\Program.cs @ 10]
23     PARAMETERS:
24         args (0x00000087DC9CE670) = 0x00000197a6008e90
25     LOCALS:
26         0x00000087DC9CE658 = 0x00000197a6009628

                  紅色標註的就是發生變化的。3 個陣列變數依然存在,3 個控制代碼變數已經釋放了。
                  我們繼續【g】執行偵錯程式,控制檯程式輸出“Press any key to Exit!”,此時,已經成功執行第二垃圾回收,我們回到偵錯程式,點選【break】按鈕,進入到中斷模式。此時,不用執行【!GCHandles】命令,控制代碼已經釋放了。我們直接去託管執行緒上下文,檢視呼叫棧,確認 3 個陣列變數已經被回收了。

 1 0:001> ~0s
 2 ntdll!NtDeviceIoControlFile+0x14:
 3 00007ffd`cf30d0c4 c3              ret
 4 
 5 0:000> !ClrStack -a
 6 OS Thread Id: 0x598 (0)
 7         Child SP               IP Call Site
 8 00000087DC9CE4C8 00007ffdcf30d0c4 [InlinedCallFrame: 00000087dc9ce4c8] Interop+Kernel32.g____PInvoke|45_0(IntPtr, INPUT_RECORD*, Int32, Int32*)
 9 00000087DC9CE4C8 00007ffc8f341e25 [InlinedCallFrame: 00000087dc9ce4c8] Interop+Kernel32.g____PInvoke|45_0(IntPtr, INPUT_RECORD*, Int32, Int32*)
10 00000087DC9CE4A0 00007ffc8f341e25 Interop+Kernel32.ReadConsoleInput(IntPtr, INPUT_RECORD ByRef, Int32, Int32 ByRef) [/_/src/libraries/System.Console/src/Microsoft.Interop.LibraryImportGenerator/Microsoft.Interop.LibraryImportGenerator/LibraryImports.g.cs @ 470]
11     PARAMETERS:
12         hConsoleInput = <no data>
13         buffer = <no data>
14         numInputRecords_UseOne = <no data>
15         numEventsRead = <no data>
16     LOCALS:
17         <no data>
18         <no data>
19         <no data>
20         <no data>
21         <no data>
22         <no data>
23         <no data>
24 
25 00000087DC9CE560 00007ffd49dfaa0a System.ConsolePal.ReadKey(Boolean) [/_/src/libraries/System.Console/src/System/ConsolePal.Windows.cs @ 334]
26     PARAMETERS:
27         intercept (<CLR reg>) = 0x0000000000000000
28     LOCALS:
29         <no data>
30         <no data>
31         <no data>
32         <no data>
33         <no data>
34         <no data>
35         <no data>
36         0x00000087DC9CE5A0 = 0x00000197a6009c98
37         <no data>
38         <no data>
39         <no data>
40         <no data>
41         <no data>
42 
43 00000087DC9CE620 00007ffc8f3419a4 ExampleCore_5_6.Program.Main(System.String[]) [E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_5_6\Program.cs @ 13]
44     PARAMETERS:
45         args (0x00000087DC9CE670) = 0x00000197a6008e90
46     LOCALS:
47         0x00000087DC9CE658 = 0x00000197a6009628

                  此時,我們看到,Run 方法的棧幀都沒有了,說明記憶體被回收了。


        3.2.7、垃圾收集模式
            垃圾收集模式公有 3 種:非併發工作站、併發工作站、伺服器。

            伺服器模式:為每個處理器建立一個堆和一個 GC 執行緒,垃圾收集操作都是透過處理器上專門的 GC 執行緒來執行的。

            非併發工作站模式:在整個垃圾收集過程中會掛起所有的託管執行緒,只有在垃圾收集操作完成後,才會恢復程序中所有的託管執行緒的執行。在不需要快速相應的情況下,這種方式還好。在需要響應速度的情況下,比如:GUI程式,也就是 WinForm、WPF等就不合適了,半天沒反應,客戶是不答應的。

            併發工作站模式:在整個垃圾收集的過程中並不會一直掛起託管執行緒,而是會定期醒來,並且在執行一些必要的工作後再重新休眠。這增加了程式的響應速度,但是會使垃圾收集操作略微變慢。

    3.3、除錯託管堆的破壞問題
        A、基礎知識
            堆破壞 是一種違背了堆完整性的錯誤,它會導致程式出現奇怪的行為。想要查詢問題也是十分困難,因為在託管堆被破壞後表現出的行為各不相同。我們期望的是,在出現堆破壞的問題時,程式就立刻出現崩潰,這樣就儘可能的保證發生崩潰的位置與發生破壞的位置相近,就不需要透過大量的棧回溯找出最初發生破壞的位置。
            雖然造成堆破壞的原因有多種,但是一個非常常見的原因就是:沒有正確的管理程式中的記憶體。例如:釋放記憶體後再次使用記憶體,懸空指標,緩衝區溢位等,都可能導致堆破壞。然而,CLR 透過高效的管理記憶體解決了很多問題。例如:一塊記憶體釋放後,將無法再次使用。緩衝區溢位也會被識別出來作為一個異常。想要這些起作用,必須保證程式碼執行在託管環境中才可以。
            
            當我們透過互用性服務呼叫非託管 API 時,傳遞給非託管 API 的資料是不受 CLR 保護的。所以說,託管程式碼與非託管程式碼的互動是在託管環境中導致堆破壞的最主要原因之一。當然,在沒有使用任何非託管程式碼的情況下也可能出現堆破壞的情況,但是,這樣的情況極少,通常說 CLR 本身存在一個錯誤。            

            當託管堆被破壞了,我們可以使用【!VerifyHeap】命令來驗證堆的完整性。該命令會遍歷整個託管堆,驗證每個物件,並且報告驗證過程的結果。

            如果還是在使用 .NET Framework 平臺,那麼使用 gcUnmanagedToManaged(包含 gcManagedToUnmanaged) 這個 MDA 就會很方便,但是,如果在跨平臺的 NET 版本中就不可以了,切記。

        B、眼見為實
            除錯原始碼:ExampleCore_5_7 和 ExampleCore_5_8(C++)
            除錯任務:透過 MDA 排查有關堆破壞的問題。
            C++ 的專案需要說明一下,專案的屬性【配置型別】是"動態庫(.dll)",輸出目錄:..\ExampleCore_5_7\bin\Debug\net8.0\。
            在這個專案裡還有一個 mda 的配置檔案,ExampleCore_5_7.exe.mda.config。

            想要啟動 MDA,需要提前做一些準備,我在”基礎知識“中,貼出了網址,如果不熟悉的大家可以自己惡補。我的操作過程是透過三步完成的,第一步,配置環境變數,第二步:配置專案的 MDA 配置檔案,第三步就可以執行測試了。
            第一步:在我的電腦裡配置環境變數(COMPLUS_MDA=1,啟動 MDA.)。
            

            第二步:我為我的程式配置了 mda 配置檔案,檔名:Example_13_1_1.exe.mda.config。配置詳情如下:

<?xml version="1.0" encoding="utf-8" ?>
<mdaConfig>
    <assistants>
        <gcManagedToUnmanaged/>
        <gcUnmanagedToManaged/>
    </assistants>
</mdaConfig>

            第三步:我沒有選擇除錯工具,直接執行 exe,並沒有看到任何錯誤,結果輸出了(說明 MDA 在 NET 平臺無效,只是針對 .NET Framework 平臺)。效果如圖:
            
            我們發現程式並沒有出現錯誤。我們透過偵錯程式試試。
            1)、NTSD 除錯
                編譯專案,開啟【Visual Studio 2022 Developer Command Prompt v17.9.6】命令列工具,輸入命令【NTSD E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_5_7\bin\Debug\net8.0\ExampleCore_5_7.exe】開啟偵錯程式。
                進入偵錯程式後,我們直接【g】執行偵錯程式,直到偵錯程式輸出如圖:
                

                按組合鍵【ctrl+c】進入中斷模式,從輸出結果中已經知道輸出是有錯誤的,並且,我們呼叫了非託管的 API,所以上來第一步我們先驗證一下託管堆有沒有問題,執行命令【!VerifyHeap】。

 1 0:003> !VerifyHeap
 2 Error reading card bits
 3 Error reading card bits
 4 Error reading card bits
 5 。。。。。。(省略了)
 6 Error reading card bits
 7 Error reading card bits
 8 Error reading card bits
 9 Error reading card bits
10 Object 0000020e00809330:  missing card_table entry for 0000020E00809338
11 Last good object: 0000020E00809318.
12 Error reading card bits
13 Error reading card bits
14 Error reading card bits
15 Error reading card bits
16 Error reading card bits
17 Error reading card bits
18 Error reading card bits
19 。。。。。。(省略了)
20 Error reading card bits
21 Error reading card bits
22 Error reading card bits
23 Error reading card bits
24 Error reading card bits
25 object 0000020dfe002020: bad member 0000020E008096C8 at 0000020DFE002030
26 Last good object: 0000020DFE000028.

                我們一看就知道有錯誤了,那麼多 Error 。雖然知道有了錯誤,我嘗試了一些方法,都沒辦法繼續下去了。我就到此為止了,Windbg Preview 測試的還是更好點,這也是我推薦在工作中使用 Windbg Preview 做除錯的原因。


            2)、Windbg Preview 除錯
                我們編譯專案,開啟【Windbg Preview】偵錯程式,依次點選【檔案】---【Launch Executable】,載入我們的專案檔案:ExampleCore_5_7.exe,進入偵錯程式後。我們直接【g】執行偵錯程式,直到我們的控制檯程式輸出結果為止。
                效果如圖:
                

                此時,我們回到偵錯程式中,點選【break】按鈕,中斷偵錯程式,開始除錯。
                我們輸入【!VerifyHeap】命令,來驗證一下託管堆是否有效。

1 0:002> !VerifyHeap
2 Segment          Object           Failure                          Reason
3 02543cbaec40     021428c02020     InvalidObjectReference           Object 21428c02020 has a bad member at offset 10: 2142b4096c8
4 02543cbaf320     02142b409648     InvalidMethodTable               Object 2142b409648 has an invalid method table 61006100610061
5 
6 229 objects verified, 2 errors.

                我們看到了,雖然程式沒崩潰,其實我們的託管堆是有問題的。位於地址 2142b409648 處的物件的方法表是無效的。我們可以很容易驗證這一點,即將這個地址上的內容透過【dp】命令顯示出來。

 1 0:002> dp 2142b409648
 2 00000214`2b409648  00610061`00610061 00610061`00610061
 3 00000214`2b409658  00610061`00610061 00610061`00610061
 4 00000214`2b409668  00610061`00610061 00610061`00610061
 5 00000214`2b409678  00610061`00610061 00610061`00610061
 6 00000214`2b409688  00610061`00610061 00610061`00610061
 7 00000214`2b409698  00610061`00610061 00610061`00610061
 8 00000214`2b4096a8  00610061`00610061 00610061`00610061
 9 00000214`2b4096b8  00610061`00610061 00610061`00610061
10 
11 0:002> dp 00610061`00610061
12 00610061`00610061  ????????`???????? ????????`????????
13 00610061`00610071  ????????`???????? ????????`????????
14 00610061`00610081  ????????`???????? ????????`????????
15 00610061`00610091  ????????`???????? ????????`????????
16 00610061`006100a1  ????????`???????? ????????`????????
17 00610061`006100b1  ????????`???????? ????????`????????
18 00610061`006100c1  ????????`???????? ????????`????????
19 00610061`006100d1  ????????`???????? ????????`????????

                此時,我們就知道了堆被破壞了。
                我們可以針對地址:2142b409648 使用【!listnearobj 2142b409648】命令列出該地址附近的物件,看看具體情況。

1 0:002> !listnearobj 2142b409648
2 Before:              02142b409628 30 (0x1e)                        System.Char[](這個就是我們宣告的 char 陣列)
3 Current:             02142b409648                                  Unknown
4 Error Detected: Object 2142b409648 has an invalid method table 61006100610061 [verify heap]
5 Next:                02142b4096f8 40 (0x28)                        System.Buffers.SharedArrayPool<System.Char>
6 Expected to find next object at 2142b409648, instead found it at 2142b4096f8.
7 Heap local consistency not confirmed.

                我們可以使用【!do 02142b409628】或者【!DumpObj 02142b409628】命令來確認我們說的對不對。

 1 0:002> !do 02142b409628
 2 Name:        System.Char[]
 3 MethodTable: 00007ffaf3ff9438
 4 EEClass:     00007ffaf3ff93b8
 5 Tracked Type: false
 6 Size:        30(0x1e) bytes
 7 Array:       Rank 1, Number of elements 3, Type Char (Print Array)
 8 Content:     aaa
 9 Fields:
10 None

                內容是 3 個 a,以前是 a、b、c 三個元素,說明資料被修改了。我們也可以使用【dp 02142b409628】命令直接輸出結果。

1 0:002> dp 02142b409628
2 00000214`2b409628  00007ffa`f3ff9438 00000000`00000003
3 00000214`2b409638  00610061`00610061 00610061`00610061
4 00000214`2b409648  00610061`00610061 00610061`00610061
5 00000214`2b409658  00610061`00610061 00610061`00610061
6 00000214`2b409668  00610061`00610061 00610061`00610061
7 00000214`2b409678  00610061`00610061 00610061`00610061
8 00000214`2b409688  00610061`00610061 00610061`00610061
9 00000214`2b409698  00610061`00610061 00610061`00610061

                00007ffa`f3ff9438 是方法表,00000000`00000003 是元素的個數:3個,0061 就是 十進位制 97(英文字母 a)。
                我們可以針對使用【!DumpArray 02142b409628】命令輸出這個陣列元素,並使用【!DumpVC】命令輸出每個元素值。

 1 0:002> !DumpArray 02142b409628
 2 Name:        System.Char[]
 3 MethodTable: 00007ffaf3ff9438
 4 EEClass:     00007ffaf3ff93b8
 5 Size:        30(0x1e) bytes
 6 Array:       Rank 1, Number of elements 3, Type Char
 7 Element Methodtable: 00007ffaf3ebb538
 8 [0] 000002142b409638(0 索引元素地址)
 9 [1] 000002142b40963a(1 索引元素地址)
10 [2] 000002142b40963c(2 索引元素地址)
11 
12 0:002> !DumpVC /d 00007ffaf3ebb538 000002142b409638
13 Name:        System.Char
14 MethodTable: 00007ffaf3ebb538
15 EEClass:     00007ffaf3ea8360
16 Size:        24(0x18) bytes
17 File:        C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.4\System.Private.CoreLib.dll
18 Fields:
19               MT    Field   Offset                 Type VT     Attr            Value Name
20 00007ffaf3ebb538  40003b2        0          System.Char  1 instance               61 m_value
21 
22 0:002> !DumpVC /d 00007ffaf3ebb538 000002142b40963a
23 Name:        System.Char
24 MethodTable: 00007ffaf3ebb538
25 EEClass:     00007ffaf3ea8360
26 Size:        24(0x18) bytes
27 File:        C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.4\System.Private.CoreLib.dll
28 Fields:
29               MT    Field   Offset                 Type VT     Attr            Value Name
30 00007ffaf3ebb538  40003b2        0          System.Char  1 instance               61 m_value
31 
32 0:002> !DumpVC /d 00007ffaf3ebb538 000002142b40963c
33 Name:        System.Char
34 MethodTable: 00007ffaf3ebb538
35 EEClass:     00007ffaf3ea8360
36 Size:        24(0x18) bytes
37 File:        C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.4\System.Private.CoreLib.dll
38 Fields:
39               MT    Field   Offset                 Type VT     Attr            Value Name
40 00007ffaf3ebb538  40003b2        0          System.Char  1 instance               61 m_value

                我們知道了字元陣列有問題,就能推斷錯誤在哪裡。

    3.4、除錯託管堆的碎片問題
        A、基礎知識
            在託管堆上,當空閒記憶體塊和被佔用記憶體塊交錯存放,就會出現記憶體碎片的問題,我們的應用程式也會出現各種問題,通常表現為丟擲 OutOfMemoryException 異常。我們如何確定有堆碎片問題呢?有一個標準可以參考,我們可以使用【空閒塊總大小與託管堆總大小的比值】,如果空閒塊佔據了大部分堆空間,那麼堆碎片可能會是一個問題。
            我們還有一個問題需要關注,堆碎片發生在哪個代。在第 0 代中通常沒有碎片,因為 CLR 堆管理器可以使用任何空閒的記憶體塊來進行分配。然而,在第 1 代和第 2代中,使用空閒塊唯一的方式就是將物件提升到各自的代中。由於第 1 代是臨時記憶體段的一部分,並且只有一個臨時記憶體段,因此,在除錯堆碎片的問題時,通常需要分析第 2 代的記憶體空間。
            當我們使用控制代碼“固定”一個物件的時候,過多的或者過久的“固定”是造成託管堆上出現碎片的最常見原因之一。如果需要固定物件,那麼我們必須保證固定的時間較短,從而不對垃圾收集器造成太多的影響。

            如果在 Windows 虛擬記憶體管理器管理的記憶體中產生了碎片,CLR 不會透過增加堆容量(增加新的記憶體段)來滿足分配需求,我們可以使用【address】命令系統虛擬記憶體狀態的詳細資訊。

            有哪些工具可以監視程序中記憶體的使用量呢?有多種選擇,最基本的方式就是使用【工作管理員】,可以按下【ctrl+shift+esc】組合鍵就可以開啟【工作管理員】,如圖:
            

            【工作管理員】只能大略的檢視記憶體使用情況,如果我們想知道消耗的記憶體是位於非託管堆上還是託管堆上?是在堆上還是在其他什麼地方,【工作管理員】就不能勝任了。此時,我們就應該使用【Windows 效能監視器】,它可以用來分析系統的整個狀態或者是每個程序的狀態。它使用了不同的資料來源,例如:效能計數器,跟蹤日誌以及配置資訊等,但是,效能監視器是用於分析 .NET Framework 應用程式的最易用的工具,跨平臺是不適合的。

            以下標紅的內容是針對 .NET Framework 平臺的,我記錄一下而已,如果想關注 .NET 8.0 此處可以略過。
            如果我們想執行【效能監視器】,可以在【執行框】中輸入【perfmon】命令,就可以開啟【效能監視器】。效果如圖:
            

            效能監視器如圖:
            

            我們可以點選左側的【效能監視器】,在右側通常顯示 Processor Time 計數器,如果要新增計數器,可以點選右鍵,選擇【新增計數器(D)】選單,開啟新增計數器的選單,按著自己需求操作就可以了。效果如圖:
            

            【新增計數器】由兩部分組成,第一步部分就是【可用計數器】選項,它包含一個 計數器種類 的下拉選單,以及可用物件的例項,並且效能計數器將在這些例項上收集和現實資料。右側皮膚會列出已經被新增的所有效能計數器。
            我們必須清楚的知道每種效能計數器的用途,才知道如何選擇,具體效能計數器的用途如圖:
            .NET CLR 資料(.NET CLR Data):關於資料(例如 SOL)效能的執行時統計資訊
            .NET CLR 異常(.NET CLR Exceptions):關於 CLR 異常處理的執行時統計資訊,例如所丟擲的異常數量
            .NET CLR 互用性(.NET CLR Interop):關於互用性服務的執行時統計,例如列集操作的次數
            .NET CLR 即時編譯器(.NET CLR Jit):關於即時編譯器的執行時統計,例如被即時編譯器編譯的方法數量
            .NET CLR 載入過程(.NET CLR Loading):關於CLR類/程式集載入器的執行時統計,例如載入器堆中的位元組總數
            .NET CLR 鎖與執行緒(.NET CLR LocksAndThreads):關於鎖和執行緒的執行時統計,例如鎖的競爭率
            .NET CLR 記憶體(.NET CLR Memory):關於託管堆和垃圾收集器的執行時統計,例如每一代中收集操作的次數
            .NET CLR 網路(.NET CLRE Networking):關於網路的執行時統計,例如已傳送和已接收的資料包
            .NET CLR 遠端操作(.NET CLR Remoting):關於遠端行為的執行時統計,例如每秒鐘發生的遠端呼叫次數
            .NET CLR 安全(.NET CLR Security):關於安全性的執行時統計,例如執行時檢查的總次數

        B、眼見為實
            B1、除錯原始碼:ExampleCore_5_9
               除錯任務:如何找到堆碎片問題並且分析發生問題的原因。
              1)、NTSD 除錯
                  編譯專案,開啟【Visual Studio 2022 Developer Command Prompt v17.9.6】命令列工具,輸入命令【NTSD E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_5_9\bin\Debug\net8.0\ExampleCore_5_9.exe】開啟偵錯程式。
                  進入到偵錯程式後,直接【g】執行偵錯程式,我們的控制檯程式輸出“<alloc size>”字樣,讓我們輸入分配記憶體的大小,我輸入 50000 位元組,回車。控制檯繼續輸出“<max mem in MB>”字樣,讓我們輸入分配記憶體的最大值,我輸入 1000 MB(也就是 1 G MB)位元組,回車。此時,我們的控制檯程式輸出“Press any key to GC & promo to gen1”字樣。效果如圖:
                  

                  按組合鍵【ctrl+c】進入中斷模式,開始我們的除錯。
                  我們先看看託管堆上的統計情況,執行【!DumpHeap -stat】命令。

 1 0:001> !DumpHeap -stat
 2 Statistics:
 3               MT    Count    TotalSize Class Name
 4 00007ff8f3036dd0        1           24 System.IO.SyncTextReader
 5 00007ff8f301f1c0        1           24 System.Threading.Tasks.Task+<>c
 6 00007ff8f301a898        1           24 System.IO.Stream+NullStream
 7 00007ff8f2fe0100        1           24 ExampleCore_5_9.Program
 8 。。。。。。(省略了)
 9 00007ff8f2fe2208        1          400 System.Diagnostics.Tracing.RuntimeEventSource
10 00007ff8f303c978        2          912 System.Globalization.CultureData
11 00007ff8f2fbfa88       17          976 System.String[]
12 00007ff8f2f19df8        8         3432 System.Int32[]
13 00007ff8f2e6c4d8        3        16544 System.Object[]
14 00007ff8f301f920        3        33356 System.Char[]
15 00007ff8f2f1ec08      187        39732 System.String
16 00007ff8f2fe9820        1       160024 System.Runtime.InteropServices.GCHandle[](我們宣告的控制代碼陣列:pinnedHandles)
17 00007ff8f2fe9610        2       160048 System.Byte[][](我們宣告的位元組陣列:nonPinned 和 pinned)
18 0000029481a7bed0    19695      1365104      Free(這是當前的記憶體空閒塊)
19 00007ff8f2fe9410    20003   1000484178 System.Byte[]
20 Total 39996 objects

                  紅色標註的已經說明的很清楚了,由於我們在關注對碎片,所以主要看標註【Free】的資料。我們可以看到記憶體空閒塊有 19695 個,所在空間的大小是 1365104 位元組,也就是 1.36 MB。有了空閒記憶體塊的大小了,我們在看看 GC 堆總的大小是多少,執行【!eeheap -gc】命令。

 1 0:001> !eeheap -gc
 2 Number of GC Heaps: 1
 3 generation 0 starts at 0x00000294D7000028
 4 generation 1 starts at 0x00000294D6000028
 5 generation 2 starts at 0x000002D518170008
 6 ephemeral segment allocation context: none
 7          segment             begin         allocated         committed    allocated size    committed size
 8 generation 0:(第 0 代)
 9 000002D49767D1E0  00000294D7000028  00000294D7000028  00000294D7321000  0x0(0)  0x320fd8(3280856)
10 generation 1:(第 1 代)
11 000002D49767CF20  00000294D6000028  00000294D63F6290  00000294D6400000  0x3f6268(4153960)  0x3fffd8(4194264)
12 000002D49767CFD0  00000294D6400028  00000294D660D690  00000294D6800000  0x20d668(2152040)  0x3fffd8(4194264)
13 000002D49767D080  00000294D6800028  00000294D6BF6290  00000294D6C00000  0x3f6268(4153960)  0x3fffd8(4194264)
14 000002D49767D130  00000294D6C00028  00000294D6E0D690  00000294D6E11000  0x20d668(2152040)  0x210fd8(2166744)
15 generation 2:(第 2 代)
16 0000029481B3EA70  000002D518170008  000002D518171D38  000002D518180000  0x1d30(7472)  0xfff8(65528)
17 000002D49766F270  0000029485C00028  0000029485CE5E90  0000029485FF6000  0xe5e68(941672)  0x3f5fd8(4153304)
18 000002D49766F320  0000029486000028  0000029486178C28  00000294863F9000  0x178c00(1543168)  0x3f8fd8(4165592)
19 000002D49766F950  0000029488400028  00000294887F6290  0000029488800000  0x3f6268(4153960)  0x3fffd8(4194264)
20 000002D49766FA00  0000029488800028  0000029488A0D690  0000029488A4E000  0x20d668(2152040)  0x24dfd8(2416600)
21 。。。。。。(太多了,省略了)
22 00000294D4E11000  0x20d668(2152040)  0x210fd8(2166744)
23 000002D49767CC60  00000294D5000028  00000294D53F6290  00000294D5400000  0x3f6268(4153960)  0x3fffd8(4194264)
24 000002D49767CD10  00000294D5400028  00000294D560D690  00000294D5800000  0x20d668(2152040)  0x3fffd8(4194264)
25 000002D49767CDC0  00000294D5800028  00000294D5BF6290  00000294D5C00000  0x3f6268(4153960)  0x3fffd8(4194264)
26 000002D49767CE70  00000294D5C00028  00000294D5E0D690  00000294D5E11000  0x20d668(2152040)  0x210fd8(2166744)
27 Large object heap starts at 0x0000000000000000
28          segment             begin         allocated         committed    allocated size    committed size
29 000002D49766F3D0  0000029486400028  0000029486427160  0000029486428000  0x27138(160056)  0x27fd8(163800)
30 Pinned object heap starts at 0x0000000000000000
31 000002D49766EC40  0000029483800028  0000029483804018  0000029483811000  0x3ff0(16368)  0x10fd8(69592)
32 Total Allocated Size:              Size: 0x3b8bc920 (999016736) bytes.
33 Total Committed Size:              Size: 0x41ba5df8 (1102732792) bytes.
34 ------------------------------
35 GC Allocated Heap Size:    Size: 0x3b8bc920 (999016736) bytes.
36 GC Committed Heap Size:    Size: 0x41ba5df8 (1102732792) bytes.

                  我們看到 GC 堆分配了 1 GB 左右的空間,空閒塊總大小是 1.36 MB,也就是堆碎片在系統的 0.1,這個比例不是很大,可以繼續執行。其實,我們在使用【!eeheap -gc】命令的輸出中,能看到第 2 代有很多的記憶體段,說明分配了很多新的記憶體。由於我們正在分配一塊非常大的記憶體,因此,臨時記憶體段會很快被填滿,並開始建立新的第 2 代記憶體段。

                  我們可以使用【!DumpHeap -type Free】命令或者【!dumpheap -mt 0000029481a7bed0】命令來找出堆上所有的空閒記憶體塊。

 1 0:001> !DumpHeap -type Free
 2 00000294cdd62590 0000029481a7bed0       24 Free
 3 00000294cdd6e910 0000029481a7bed0       24 Free
 4 00000294cdd7ac90 0000029481a7bed0       24 Free
 5 00000294cdd87010 0000029481a7bed0       24 Free
 6 00000294cdd93390 0000029481a7bed0       24 Free
 7 00000294cdd9f710 0000029481a7bed0       24 Free
 8 。。。。。。。(省略了)
 9 00000294d2de8c10 0000029481a7bed0       24 Free
10 00000294d2df4f90 0000029481a7bed0       24 Free
11 00000294d2e01310 0000029481a7bed0       24 Free
12 00000294d300c390 0000029481a7bed0       24 Free
13 00000294d3018710 0000029481a7bed0       24 Free
14 00000294d3024a90 0000029481a7bed0       24 Free
15 00000294d3030e10 0000029481a7bed0       24 Free
16 00000294d303d190 0000029481a7bed0       24 Free
17 。。。。。。(省略了)
18 00000294d5a93d10 0000029481a7bed0       24 Free
19 00000294d5aa0090 0000029481a7bed0       24 Free
20 00000294d5aac410 0000029481a7bed0       24 Free
21 00000294d5ab8790 0000029481a7bed0       24 Free
22 00000294d5ac4b10 0000029481a7bed0       24 Free
23 00000294d5ad0e90 0000029481a7bed0       24 Free
24 00000294d5add210 0000029481a7bed0       24 Free
25 00000294d5ae9590 0000029481a7bed0       24 Free
26 00000294d5af5910 0000029481a7bed0       24 Free
27 。。。。。。(省略了)
28 00000294d5daba90 0000029481a7bed0       24 Free
29 00000294d5db7e10 0000029481a7bed0       24 Free
30 00000294d5dc4190 0000029481a7bed0       24 Free
31 00000294d5dd0510 0000029481a7bed0       24 Free
32 00000294d5ddc890 0000029481a7bed0       24 Free
33 00000294d5de8c10 0000029481a7bed0       24 Free
34 00000294d5df4f90 0000029481a7bed0       24 Free
35 00000294d5e01310 0000029481a7bed0       24 Free
36 0000029486400028 0000029481a7bed0       32 Free
37 
38 Statistics:
39               MT    Count    TotalSize Class Name
40 0000029481a7bed0    19695      1365104      Free
41 Total 19695 objects
42 0:001>

                  另外【!dumpheap -mt】命令的結果類似。

 1 0:001> !dumpheap -mt 0000029481a7bed0
 2 00000294bca87990 0000029481a7bed0       24 Free
 3 00000294bca93d10 0000029481a7bed0       24 Free
 4 00000294bcaa0090 0000029481a7bed0       24 Free
 5 00000294bcaac410 0000029481a7bed0       24 Free
 6 00000294bcab8790 0000029481a7bed0       24 Free
 7 00000294bcac4b10 0000029481a7bed0       24 Free
 8 00000294bcad0e90 0000029481a7bed0       24 Free
 9 00000294bcadd210 0000029481a7bed0       24 Free
10 00000294bcae9590 0000029481a7bed0       24 Free
11 00000294bcaf5910 0000029481a7bed0       24 Free
12 00000294bcb01c90 0000029481a7bed0       24 Free
13 。。。。。。
14 00000294c7b1a390 0000029481a7bed0       24 Free
15 00000294c7b26710 0000029481a7bed0       24 Free
16 00000294c7b32a90 0000029481a7bed0       24 Free
17 00000294c7b3ee10 0000029481a7bed0       24 Free
18 00000294c7b4b190 0000029481a7bed0       24 Free
19 00000294c7b57510 0000029481a7bed0       24 Free
20 00000294c7b63890 0000029481a7bed0       24 Free
21 00000294c7b6fc10 0000029481a7bed0       24 Free
22 00000294c7b7bf90 0000029481a7bed0       24 Free
23 00000294c7b88310 0000029481a7bed0       24 Free
24 。。。。。。
25 00000294d026f290 0000029481a7bed0       24 Free
26 00000294d027b610 0000029481a7bed0       24 Free
27 00000294d0287990 0000029481a7bed0       24 Free
28 00000294d0293d10 0000029481a7bed0       24 Free
29 00000294d02a0090 0000029481a7bed0       24 Free
30 00000294d02ac410 0000029481a7bed0       24 Free
31 00000294d02b8790 0000029481a7bed0       24 Free
32 00000294d02c4b10 0000029481a7bed0       24 Free
33 00000294d02d0e90 0000029481a7bed0       24 Free
34 00000294d02dd210 0000029481a7bed0       24 Free
35 00000294d02e9590 0000029481a7bed0       24 Free
36 。。。。。。
37 00000294d5daba90 0000029481a7bed0       24 Free
38 00000294d5db7e10 0000029481a7bed0       24 Free
39 00000294d5dc4190 0000029481a7bed0       24 Free
40 00000294d5dd0510 0000029481a7bed0       24 Free
41 00000294d5ddc890 0000029481a7bed0       24 Free
42 00000294d5de8c10 0000029481a7bed0       24 Free
43 00000294d5df4f90 0000029481a7bed0       24 Free
44 00000294d5e01310 0000029481a7bed0       24 Free
45 0000029486400028 0000029481a7bed0       32 Free
46 
47 Statistics:
48               MT    Count    TotalSize Class Name
49 0000029481a7bed0    19695      1365104      Free
50 Total 19695 objects
51 0:001>

                  我們【g】繼續測試,恢復程式的執行,如果提示,就按任意鍵繼續,直到控制檯程式輸出“Press any key to Exit”字樣為止。
                  效果如圖:
                  

                  此時,我們點選【ctrl+c】組合鍵進入中斷模式,輸入命令【!DumpHeap -stat】統計一下託管堆的情況。

 1 0:001> !DumpHeap -stat
 2 Statistics:
 3               MT    Count    TotalSize Class Name
 4 00007ff8f3036dd0        1           24 System.IO.SyncTextReader
 5 00007ff8f301f1c0        1           24 System.Threading.Tasks.Task+<>c
 6 00007ff8f301a898        1           24 System.IO.Stream+NullStream
 7 00007ff8f2fe0100        1           24 ExampleCore_5_9.Program
 8 00007ff8f2fbca50        1           24 System.OrdinalIgnoreCaseComparer
 9 。。。。。。(省略了)
10 00007ff8f2fe2208        1          400 System.Diagnostics.Tracing.RuntimeEventSource
11 00007ff8f303c978        2          912 System.Globalization.CultureData
12 00007ff8f2fbfa88       17          976 System.String[]
13 00007ff8f2f19df8        8         3432 System.Int32[]
14 00007ff8f2e6c4d8        3        16544 System.Object[]
15 00007ff8f301f920        3        33356 System.Char[]
16 00007ff8f2f1ec08      187        39714 System.String
17 00007ff8f2fe9820        1       160024 System.Runtime.InteropServices.GCHandle[](我們宣告的控制代碼陣列:pinnedHandles,沒什麼變化)
18 00007ff8f2fe9610        2       160048 System.Byte[][](我們宣告的位元組陣列:nonPinned 和 pinned,也沒變化)
19 0000029481a7bed0     9846    492996800      Free(記憶體空閒塊,變化了,空間增大了,以前是 1.36 MB,492 MB)
20 00007ff8f2fe9410    10004    500294202 System.Byte[]
21 Total 20148 objects

                  我們這次看到了記憶體空閒塊發生了很大的變化,數量變化不大,以前是 19695,現在是 9846,數量變少了。記憶體空間變大了,以前是 1.36 MB,現在是 492 MB。
                  我們在把 GC 堆分配的記憶體輸出出來,檢視一下。執行命令【!eeheap -gc】。

 1 0:001> !eeheap -gc
 2 Number of GC Heaps: 1
 3 generation 0 starts at 0x00000294D7C00028
 4 generation 1 starts at 0x00000294D7800028
 5 generation 2 starts at 0x000002D518170008
 6 ephemeral segment allocation context: none
 7          segment             begin         allocated         committed    allocated size    committed size
 8 generation 0:
 9 000002D49767D3F0  00000294D7C00028  00000294D7C00028  00000294D7C01000  0x0(0)  0xfd8(4056)
10 generation 1:
11 000002D49767D340  00000294D7800028  00000294D7800028  00000294D7801000  0x0(0)  0xfd8(4056)
12 generation 2:
13 0000029481B3EA70  000002D518170008  000002D518171D58  000002D518180000  0x1d50(7504)  0xfff8(65528)
14 000002D49766F270  0000029485C00028  0000029485C470F8  0000029485C68000  0x470d0(291024)  0x67fd8(425944)
15 000002D49766F320  0000029486000028  0000029486178C28  0000029486199000  0x178c00(1543168)  0x198fd8(1675224)
16 。。。。。。(省略了)
17 000002D49767CDC0  00000294D5800028  00000294D5BF6290  00000294D5C00000  0x3f6268(4153960)  0x3fffd8(4194264)
18 000002D49767CE70  00000294D5C00028  00000294D5E01310  00000294D5E11000  0x2012e8(2101992)  0x210fd8(2166744)
19 000002D49767CF20  00000294D6000028  00000294D63F6290  00000294D6400000  0x3f6268(4153960)  0x3fffd8(4194264)
20 000002D49767CFD0  00000294D6400028  00000294D6601310  00000294D662E000  0x2012e8(2101992)  0x22dfd8(2285528)
21 000002D49767D080  00000294D6800028  00000294D6BF6290  00000294D6C00000  0x3f6268(4153960)  0x3fffd8(4194264)
22 000002D49767D130  00000294D6C00028  00000294D6E01310  00000294D6E11000  0x2012e8(2101992)  0x210fd8(2166744)
23 000002D49767D1E0  00000294D7000028  00000294D731A390  00000294D7321000  0x31a368(3253096)  0x320fd8(3280856)
24 Large object heap starts at 0x0000000000000000
25          segment             begin         allocated         committed    allocated size    committed size
26 000002D49766F3D0  0000029486400028  0000029486427160  0000029486428000  0x27138(160056)  0x27fd8(163800)
27 Pinned object heap starts at 0x0000000000000000
28 000002D49766EC40  0000029483800028  0000029483804018  0000029483811000  0x3ff0(16368)  0x10fd8(69592)
29 Total Allocated Size:              Size: 0x3b3ad610 (993711632) bytes.
30 Total Committed Size:              Size: 0x3d259da8 (1025875368) bytes.
31 ------------------------------
32 GC Allocated Heap Size:    Size: 0x3b3ad610 (993711632) bytes.
33 GC Committed Heap Size:    Size: 0x3d259da8 (1025875368) bytes.

                  GC 堆的大小是 1 GB 左右,空閒記憶體塊佔據將近 500 MB 大小,說明現在堆的碎片率是 50%了,可以使用【!DumpHeap】命令確認這一點。

0:001> !DumpHeap
  1 00000294b3987028 00007ff8f2fe9410    50024
  2 00000294b3993390 0000029481a7bed0    50072 Free
  3 00000294b399f728 00007ff8f2fe9410    50024
  4 00000294b39aba90 0000029481a7bed0    50072 Free
  5 00000294b39b7e28 00007ff8f2fe9410    50024
  6 00000294b39c4190 0000029481a7bed0    50072 Free
  7 00000294b39d0528 00007ff8f2fe9410    50024
  8 00000294b39dc890 0000029481a7bed0    50072 Free
  9 00000294b39e8c28 00007ff8f2fe9410    50024
 10 00000294b39f4f90 0000029481a7bed0    50072 Free
 11 00000294b3a01328 00007ff8f2fe9410    50024
 12 00000294b3a0d690 0000029481a7bed0    50072 Free
 13 00000294b3a19a28 00007ff8f2fe9410    50024
 14 00000294b3a25d90 0000029481a7bed0    50072 Free
 15 00000294b3a32128 00007ff8f2fe9410    50024
 16 00000294b3a3e490 0000029481a7bed0    50072 Free
 17 00000294b3a4a828 00007ff8f2fe9410    50024
 18 00000294b3a56b90 0000029481a7bed0    50072 Free
 19 00000294b3a62f28 00007ff8f2fe9410    50024
 20 00000294b3a6f290 0000029481a7bed0    50072 Free
 21 00000294b3a7b628 00007ff8f2fe9410    50024
 22 00000294b3a87990 0000029481a7bed0    50072 Free
 23 00000294b3a93d28 00007ff8f2fe9410    50024
 24 00000294b3aa0090 0000029481a7bed0    50072 Free
 25 00000294b3aac428 00007ff8f2fe9410    50024
 26 00000294b3ab8790 0000029481a7bed0    50072 Free
 27 00000294b3ac4b28 00007ff8f2fe9410    50024
 28 00000294b3ad0e90 0000029481a7bed0    50072 Free
 29 00000294b3add228 00007ff8f2fe9410    50024
 30 00000294b3ae9590 0000029481a7bed0    50072 Free
 31 00000294b3af5928 00007ff8f2fe9410    50024
 32 00000294b3b01c90 0000029481a7bed0    50072 Free
 33 00000294b3b0e028 00007ff8f2fe9410    50024
 34 00000294b3b1a390 0000029481a7bed0    50072 Free
 35 00000294b3b26728 00007ff8f2fe9410    50024
 36 00000294b3b32a90 0000029481a7bed0    50072 Free
 37 00000294b3b3ee28 00007ff8f2fe9410    50024
 38 00000294b3b4b190 0000029481a7bed0    50072 Free
 39 00000294b3b57528 00007ff8f2fe9410    50024
 40 00000294b3b63890 0000029481a7bed0    50072 Free
 41 00000294b3b6fc28 00007ff8f2fe9410    50024
 42 。。。。。。(省略了)
 43 00000294c5100990 0000029481a7bed0    50072 Free
 44 00000294c510cd28 00007ff8f2fe9410    50024
 45 00000294c5119090 0000029481a7bed0    50072 Free
 46 00000294c5125428 00007ff8f2fe9410    50024
 47 00000294c5131790 0000029481a7bed0    50072 Free
 48 00000294c513db28 00007ff8f2fe9410    50024
 49 00000294c5149e90 0000029481a7bed0    50072 Free
 50 00000294c5156228 00007ff8f2fe9410    50024
 51 00000294c5162590 0000029481a7bed0    50072 Free
 52 00000294c516e928 00007ff8f2fe9410    50024
 53 00000294c517ac90 0000029481a7bed0    50072 Free
 54 00000294c5187028 00007ff8f2fe9410    50024
 55 00000294c5193390 0000029481a7bed0    50072 Free
 56 00000294c519f728 00007ff8f2fe9410    50024
 57 00000294c51aba90 0000029481a7bed0    50072 Free
 58 00000294c51b7e28 00007ff8f2fe9410    50024
 59 00000294c51c4190 0000029481a7bed0    50072 Free
 60 00000294c51d0528 00007ff8f2fe9410    50024
 61 00000294c51dc890 0000029481a7bed0    50072 Free
 62 00000294c51e8c28 00007ff8f2fe9410    50024
 63 00000294c51f4f90 0000029481a7bed0    50072 Free
 64 00000294c5201328 00007ff8f2fe9410    50024
 65 00000294c520d690 0000029481a7bed0    50072 Free
 66 00000294c5219a28 00007ff8f2fe9410    50024
 67 00000294c5225d90 0000029481a7bed0    50072 Free
 68 00000294c5232128 00007ff8f2fe9410    50024
 69 00000294c523e490 0000029481a7bed0    50072 Free
 70 00000294c524a828 00007ff8f2fe9410    50024
 71 。。。。。。(省略了)
 72 00000294d7256b90 0000029481a7bed0    50072 Free
 73 00000294d7262f28 00007ff8f2fe9410    50024
 74 00000294d726f290 0000029481a7bed0    50072 Free
 75 00000294d727b628 00007ff8f2fe9410    50024
 76 00000294d7287990 0000029481a7bed0    50072 Free
 77 00000294d7293d28 00007ff8f2fe9410    50024
 78 00000294d72a0090 0000029481a7bed0    50072 Free
 79 00000294d72ac428 00007ff8f2fe9410    50024
 80 00000294d72b8790 0000029481a7bed0    50072 Free
 81 00000294d72c4b28 00007ff8f2fe9410    50024
 82 00000294d72d0e90 0000029481a7bed0    50072 Free
 83 00000294d72dd228 00007ff8f2fe9410    50024
 84 00000294d72e9590 0000029481a7bed0    50072 Free
 85 00000294d72f5928 00007ff8f2fe9410    50024
 86 00000294d7301c90 0000029481a7bed0    50072 Free
 87 00000294d730e028 00007ff8f2fe9410    50024
 88 0000029486400028 0000029481a7bed0       32 Free
 89 0000029486400048 00007ff8f2fe9820   160024
 90 0000029483800028 00007ff8f2e6c4d8     8184
 91 0000029483802020 00007ff8f2e6c4d8     8184
 92 
 93 Statistics:
 94               MT    Count    TotalSize Class Name
 95 00007ff8f3036dd0        1           24 System.IO.SyncTextReader
 96 00007ff8f301f1c0        1           24 System.Threading.Tasks.Task+<>c
 97 00007ff8f301a898        1           24 System.IO.Stream+NullStream
 98 00007ff8f2fe0100        1           24 ExampleCore_5_9.Program
 99 。。。。。。(省略了)
100 00007ff8f303c978        2          912 System.Globalization.CultureData
101 00007ff8f2fbfa88       17          976 System.String[]
102 00007ff8f2f19df8        8         3432 System.Int32[]
103 00007ff8f2e6c4d8        3        16544 System.Object[]
104 00007ff8f301f920        3        33356 System.Char[]
105 00007ff8f2f1ec08      187        39714 System.String
106 00007ff8f2fe9820        1       160024 System.Runtime.InteropServices.GCHandle[]
107 00007ff8f2fe9610        2       160048 System.Byte[][]
108 0000029481a7bed0     9846    492996800      Free
109 00007ff8f2fe9410    10004    500294202 System.Byte[]
110 Total 20148 objects
111 0:001>

                  我們主要關注標紅的,它們是交錯分佈的,一個空閒物件,一個有物件,出現這種情況,一般來說,就是物件被固定了,不能移動,GC 無法執行緊縮和合並操作,我們可以使用【!GCHandles】命令,檢視一下控制代碼的使用情況。

 1 0:001> !GCHandles
 2 000002948196C050 Pinned      00000294B3DF4FA8    50024                  System.Byte[]
 3 000002948196C058 Pinned      00000294B3DDC8A8    50024                  System.Byte[]
 4 000002948196C060 Pinned      00000294B3DC41A8    50024                  System.Byte[]
 5 000002948196C068 Pinned      00000294B3DABAA8    50024                  System.Byte[]
 6 。。。。。。(省略了)
 7 000002948196FC00 Pinned      00000294C33B9128    50024                  System.Byte[]
 8 000002948196FC08 Pinned      00000294C33A0A28    50024                  System.Byte[]
 9 000002948196FC10 Pinned      00000294C3388328    50024                  System.Byte[]
10 000002948196FC18 Pinned      00000294C336FC28    50024                  System.Byte[]
11 000002948196FC20 Pinned      00000294C3357528    50024                  System.Byte[]
12 000002948196FC28 Pinned      00000294C333EE28    50024                  System.Byte[]
13 000002948196FC30 Pinned      00000294C3326728    50024                  System.Byte[]
14 。。。。。。(省略了)
15 0000029481A23B18 Pinned      00000294CEA93D28    50024                  System.Byte[]
16 0000029481A23B20 Pinned      00000294CEA7B628    50024                  System.Byte[]
17 0000029481A23B28 Pinned      00000294CEA62F28    50024                  System.Byte[]
18 0000029481A23B30 Pinned      00000294CEA4A828    50024                  System.Byte[]
19 0000029481A23B38 Pinned      00000294CEA32128    50024                  System.Byte[]
20 0000029481A23B40 Pinned      00000294CEA19A28    50024                  System.Byte[]
21 0000029481A23B48 Pinned      00000294CEA01328    50024                  System.Byte[]
22 。。。。。。(省略了)
23 0000029481A25D90 Pinned      00000294D72C4B28    50024                  System.Byte[]
24 0000029481A25D98 Pinned      00000294D72AC428    50024                  System.Byte[]
25 0000029481A25DA0 Pinned      00000294D7293D28    50024                  System.Byte[]
26 0000029481A25DA8 Pinned      00000294D727B628    50024                  System.Byte[]
27 0000029481A25DB0 Pinned      00000294D7262F28    50024                  System.Byte[]
28 0000029481A25DB8 Pinned      00000294D724A828    50024                  System.Byte[]
29 0000029481A25DC0 Pinned      00000294D7232128    50024                  System.Byte[]
30 0000029481A25DC8 Pinned      00000294D7219A28    50024                  System.Byte[]
31 0000029481A25DD0 Pinned      00000294D7201328    50024                  System.Byte[]
32 0000029481A25DD8 Pinned      00000294D71E8C28    50024                  System.Byte[]
33 0000029481A25DE0 Pinned      00000294D71D0528    50024                  System.Byte[]
34 0000029481A25DE8 Pinned      00000294D71B7E28    50024                  System.Byte[]
35 0000029481A25DF0 Pinned      00000294D719F728    50024                  System.Byte[]
36 0000029481A25DF8 Pinned      00000294D7187028    50024                  System.Byte[]
37 
38 Statistics:
39               MT    Count    TotalSize Class Name
40 00007ff8f2e65fa8        1           24 System.Object
41 00007ff8f2f19df8        1           96 System.Int32[]
42 00007ff8f2fe37b8        2          128 System.Diagnostics.Tracing.EventPipeEventProvider
43 00007ff8f2fb0c90        1          128 System.ExecutionEngineException
44 00007ff8f2fb0b90        1          128 System.StackOverflowException
45 00007ff8f2fb0a90        1          128 System.OutOfMemoryException
46 00007ff8f2fe3a20        2          176 System.Diagnostics.Tracing.EtwEventProvider
47 00007ff8f2fe85d0        1          184 System.Diagnostics.Tracing.NativeRuntimeEventSource
48 00007ff8f2fe2b28        4          256 System.Diagnostics.Tracing.EventSource+OverrideEventProvider
49 00007ff8f2fe2208        1          400 System.Diagnostics.Tracing.RuntimeEventSource
50 00007ff8f2e6c4d8        3        16544 System.Object[]
51 00007ff8f2fe9410    10000    500240000 System.Byte[](這裡說明了有 10000 個 Pinned 控制代碼固定住了位元組陣列)
52 Total 10018 objects
53 
54 Handles:
55     Strong Handles:       11
56     Pinned Handles:       10001
57     Weak Short Handles:   6
58 0:001>

                  我們主要關注紅色部分,其實我省略了很多。我們看到 Pinned 型別的控制代碼有 10001 個。上面的內容我註釋的很清楚,就不過多的解釋了。

                  
              2)、Windbg Preview 除錯
                  編譯專案,開啟【Windbg Preview】偵錯程式,依次點選【檔案】---【Launch executable】,載入我們的專案檔案:ExampleCore_5_9.exe,進入到偵錯程式。
                  進入到偵錯程式後,直接【g】執行偵錯程式,我們的控制檯程式輸出“<alloc size>”字樣,讓我們輸入分配記憶體的大小,我輸入 50000 位元組,回車。控制檯繼續輸出“<max mem in MB>”字樣,讓我們輸入分配記憶體的最大值,我輸入 1000 MB(也就是 1 G MB)位元組,回車。此時,我們的控制檯程式輸出“Press any key to GC & promo to gen1”字樣。效果如圖:
                  

                  此時,我們回到偵錯程式,點選【Break】按鈕進入中斷模式,開始我們的除錯之旅。
                  我們先看看託管堆上的統計情況,執行【!DumpHeap -stat】命令。

 1 0:001> !DumpHeap -stat
 2 Statistics:
 3           MT  Count     TotalSize Class Name
 4 7fff35d3ad28      1            24 System.Collections.Generic.GenericEqualityComparer<System.String>
 5 7fff35d3c7d0      1            24 System.OrdinalCaseSensitiveComparer
 6 7fff35d3c108      1            24 System.Collections.Generic.NonRandomizedStringEqualityComparer+OrdinalIgnoreCaseComparer
。。。。。。(省略了)53 7fff35d62b28      4           256 System.Diagnostics.Tracing.EventSource+OverrideEventProvider
54 7fff35d38ef8      1           288 System.Collections.Generic.Dictionary<System.String, System.Object>+Entry[]
55 7fff35dbaa30      1           312 System.Globalization.NumberFormatInfo
56 7fff35d62208      1           400 System.Diagnostics.Tracing.RuntimeEventSource
57 7fff35dbc978      2           912 System.Globalization.CultureData
58 7fff35d3fa88     17           976 System.String[]
59 7fff35c99df8      8         3,432 System.Int32[]
60 7fff35bec4d8      3        16,544 System.Object[]
61 7fff35d9f920      3        33,356 System.Char[]
62 7fff35c9ec08    187        39,732 System.String
63 7fff35d69820      1       160,024 System.Runtime.InteropServices.GCHandle[](我們宣告的控制代碼變數陣列:pinnedHandles)
64 7fff35d69610      2       160,048 System.Byte[][](我們宣告的位元組陣列:nonPinned 和 pinned)
65 01e7e9acc5b0 19,695     1,365,104 Free
66 7fff35d69410 20,003 1,000,484,178 System.Byte[]
67 Total 39,996 objects, 1,002,269,364 bytes

                  由於我們在除錯堆碎片的問題,所以要特別關注標註為【Free】的資料,我們看到了共有 19695 個空閒記憶體塊,佔用總大小為 1365104 位元組,也就是 1.36 MB。

                  接下來,我們看看堆碎片在哪個代上,使用【!eeheap -gc】命令。

 1 0:001> !eeheap -gc
 2 
 3 ========================================
 4 Number of GC Heaps: 1
 5 ----------------------------------------
 6 Small object heap
 7          segment            begin        allocated        committed allocated size     committed size    
 8 generation 0:
 9     0227ff4ad1e0     01e83f000028     01e83f31c3c8     01e83f321000 0x31c3a0 (3261344) 0x321000 (3280896)
10 generation 1:
11     0227ff4acf20     01e83e000028     01e83e3f6290     01e83e400000 0x3f6268 (4153960) 0x400000 (4194304)
12     0227ff4acfd0     01e83e400028     01e83e60d690     01e83e800000 0x20d668 (2152040) 0x400000 (4194304)
13     0227ff4ad080     01e83e800028     01e83ebf6290     01e83ec00000 0x3f6268 (4153960) 0x400000 (4194304)
14     0227ff4ad130     01e83ec00028     01e83ee0d690     01e83ee11000 0x20d668 (2152040) 0x211000 (2166784)
15 generation 2:
16     0227ff49f1c0     01e7ed800028     01e7edbf6290     01e7edc00000 0x3f6268 (4153960) 0x400000 (4194304)
17     0227ff49f270     01e7edc00028     01e7edce5e90     01e7edff6000 0xe5e68 (941672)   0x3f6000 (4153344)
18     0227ff49f320     01e7ee000028     01e7ee178c28     01e7ee3f9000 0x178c00 (1543168) 0x3f9000 (4165632)
19     。。。。。。(省略了)
20     。。。。。。(省略了)
21     。。。。。。(省略了)
22     0227ff4aca50     01e83c400028     01e83c60d690     01e83c800000 0x20d668 (2152040) 0x400000 (4194304)
23     0227ff4acb00     01e83c800028     01e83cbf6290     01e83cc00000 0x3f6268 (4153960) 0x400000 (4194304)
24     0227ff4acbb0     01e83cc00028     01e83ce0d690     01e83ce11000 0x20d668 (2152040) 0x211000 (2166784)
25     0227ff4acc60     01e83d000028     01e83d3f6290     01e83d400000 0x3f6268 (4153960) 0x400000 (4194304)
26     0227ff4acd10     01e83d400028     01e83d60d690     01e83d800000 0x20d668 (2152040) 0x400000 (4194304)
27     0227ff4acdc0     01e83d800028     01e83dbf6290     01e83dc00000 0x3f6268 (4153960) 0x400000 (4194304)
28     0227ff4ace70     01e83dc00028     01e83de0d690     01e83de11000 0x20d668 (2152040) 0x211000 (2166784)
29 NonGC heap
30          segment            begin        allocated        committed allocated size     committed size    
31     01e7e9aa4100     02287ffa0008     02287ffa1d38     02287ffb0000 0x1d30 (7472)      0x10000 (65536)   
32 Large object heap
33          segment            begin        allocated        committed allocated size     committed size    
34     0227ff49f3d0     01e7ee400028     01e7ee427160     01e7ee428000 0x27138 (160056)   0x28000 (163840)  
35 Pinned object heap
36          segment            begin        allocated        committed allocated size     committed size    
37     0227ff49ec40     01e7eb800028     01e7eb804018     01e7eb811000 0x3ff0 (16368)     0x11000 (69632)   
38 ------------------------------
39 GC Allocated Heap Size:    Size: 0x3bbd8cc0 (1002278080) bytes.
40 GC Committed Heap Size:    Size: 0x41bba000 (1102815232) bytes.

                  紅色標註的指出,GC 堆的總大小剛好在 1G 左右。從輸出我們注意到,有一個非常大的記憶體段列表。由於我們正在分配一塊非常大的記憶體,因此,臨時記憶體段會很快被填滿,並開始建立新的第 2 代記憶體段。
                  我們可以使用【!DumpHeap -type Free】命令或者【!dumpheap -mt 1e7e9acc5b0】命令來找出堆上所有的空閒記憶體塊。

 1 0:001> !DumpHeap -type Free
 2          Address               MT           Size
 3     01e7ed80c390     01e7e9acc5b0             24 Free
 4     01e7ed818710     01e7e9acc5b0             24 Free
 5     01e7ed824a90     01e7e9acc5b0             24 Free
 6     01e7ed830e10     01e7e9acc5b0             24 Free
 7     01e7ed83d190     01e7e9acc5b0             24 Free
 8     01e7ed849510     01e7e9acc5b0             24 Free
 9     。。。。。。(省略了)
10     01e83f2f5910     01e7e9acc5b0             24 Free
11     01e83f301c90     01e7e9acc5b0             24 Free
12     01e83f30e010     01e7e9acc5b0             24 Free
13     01e83f31a390     01e7e9acc5b0             24 Free
14 
15 Statistics:
16           MT  Count TotalSize Class Name
17 01e7e9acc5b0 19,695 1,365,104 Free
18 Total 19,695 objects, 1,365,104 bytes

                  兩個命令輸出差不多,

 1 0:001> !dumpheap -mt 1e7e9acc5b0
 2          Address               MT           Size
 3     01e7ed80c390     01e7e9acc5b0             24 Free
 4     01e7ed818710     01e7e9acc5b0             24 Free
 5     01e7ed824a90     01e7e9acc5b0             24 Free
 6     01e7ed830e10     01e7e9acc5b0             24 Free
 7     01e7ed83d190     01e7e9acc5b0             24 Free
 8     。。。。。。(省略了)
 9     01e83f2f5910     01e7e9acc5b0             24 Free
10     01e83f301c90     01e7e9acc5b0             24 Free
11     01e83f30e010     01e7e9acc5b0             24 Free
12     01e83f31a390     01e7e9acc5b0             24 Free
13 
14 Statistics:
15           MT  Count TotalSize Class Name
16 01e7e9acc5b0 19,695 1,365,104 Free
17 Total 19,695 objects, 1,365,104 bytes

                  我們可以拿這些 Free 塊的地址和第 2 代中的起始地址比較,就知道,這些空閒物件大部分都在第 2 代中。在堆中總共的空閒塊大小為 1365104 位元組,也就是 1.36 MB,共 19695 個,而堆的大小為 1 GB,因此,現在堆碎片佔據的比例還是比較小的,不用擔心碎片問題。

                  我們【g】繼續測試,恢復程式的執行,如果提示,就按任意鍵繼續,直到控制檯程式輸出“Press any key to Exit”字樣為止。
                  效果如圖:
                  

                  此時,我們點選【Break】按鈕進入中斷模式,輸入命令【!DumpHeap -stat】統計一下託管堆的情況。

 1 0:001> !DumpHeap -stat
 2 Statistics:
 3           MT  Count   TotalSize Class Name
 4 7fff35d3ad28      1          24 System.Collections.Generic.GenericEqualityComparer<System.String>
 5 7fff35d3c7d0      1          24 System.OrdinalCaseSensitiveComparer
 6 7fff35d3c108      1          24 System.Collections.Generic.NonRandomizedStringEqualityComparer+OrdinalIgnoreCaseComparer
 7 7fff35d3ca50      1          24 System.OrdinalIgnoreCaseComparer
 8 7fff35d60100      1          24 ExampleCore_5_9.Program
 9 7fff35d9a898      1          24 System.IO.Stream+NullStream
10 7fff35d9f1c0      1          24 System.Threading.Tasks.Task+<>c
11 7fff35db6dd0      1          24 System.IO.SyncTextReader
12 7fff35dbcb00      1          26 System.Globalization.CalendarId[]
13 7fff35d63038      1          32 System.Diagnostics.Tracing.ActivityTracker
14 7fff35d66300      1          32 System.Collections.Generic.List<System.WeakReference<System.Diagnostics.Tracing.EventSource>>
15 7fff35d61818      1          32 System.Guid
16 7fff35d671e0      1          40 System.WeakReference<System.Diagnostics.Tracing.EventSource>[]
17 7fff35d9de68      1          40 System.Threading.Tasks.TaskFactory
18 7fff35d9f5e0      1          40 System.IO.TextWriter+NullTextWriter
19 7fff35d6eea0      1          40 Interop+INPUT_RECORD
20 7fff35d3bfc8      2          48 System.Collections.Generic.NonRandomizedStringEqualityComparer+OrdinalComparer
21 7fff35d62338      2          48 System.Diagnostics.Tracing.TraceLoggingEventHandleTable
22 7fff35d656f8      2          48 System.WeakReference<System.Diagnostics.Tracing.EventSource>
23 7fff35d91428      1          48 System.Reflection.RuntimeAssembly
24 7fff35d93040      1          48 System.Text.UTF8Encoding+UTF8EncodingSealed
25 7fff35d93658      2          48 System.Text.EncoderReplacementFallback
26 7fff35d933d0      2          48 System.Text.DecoderReplacementFallback
27 7fff35db20f8      1          48 System.Text.OSEncoder
28 7fff35db4278      1          48 System.IO.TextWriter+SyncTextWriter
29 7fff35d9ad40      1          56 System.Text.ConsoleEncoding
30 7fff35db6b50      1          56 System.Text.DecoderDBCS
31 7fff35d9cfd0      1          64 System.Threading.ContextCallback
32 7fff35d9edb8      1          72 System.Threading.Tasks.Task<System.Threading.Tasks.VoidTaskResult>
33 7fff35dbd750      1          80 System.Collections.Generic.Dictionary<System.String, System.Globalization.CultureData>
34 7fff35d347c8      1          80 System.Collections.Generic.Dictionary<System.String, System.Object>
35 7fff35bea318      2          80 System.RuntimeType
36 7fff35d63d00      4          96 System.WeakReference<System.Diagnostics.Tracing.EventProvider>
37 7fff35d94378      2          96 System.ConsolePal+WindowsConsoleStream
38 7fff35db4e08      1          96 System.IO.StreamReader
39 7fff35dbf910      1          96 System.Collections.Generic.Dictionary<System.String, System.Globalization.CultureData>+Entry[]
40 7fff35d9a3c8      1         104 System.IO.StreamWriter
41 7fff35be5fa8      5         120 System.Object
42 7fff35d30a90      1         128 System.OutOfMemoryException
43 7fff35d30b90      1         128 System.StackOverflowException
44 7fff35d30c90      1         128 System.ExecutionEngineException
45 7fff35d637b8      2         128 System.Diagnostics.Tracing.EventPipeEventProvider
46 7fff35d91dd8      2         128 System.Text.OSEncoding
47 7fff35dbd370      1         160 System.Globalization.CalendarData
48 7fff35d63a20      2         176 System.Diagnostics.Tracing.EtwEventProvider
49 7fff35d685d0      1         184 System.Diagnostics.Tracing.NativeRuntimeEventSource
50 7fff35d62410      2         208 System.IntPtr[]
51 7fff35dbd3f0      1         208 System.Globalization.CalendarData[]
52 7fff35dbb3a8      2         224 System.Globalization.CultureInfo
53 7fff35d62b28      4         256 System.Diagnostics.Tracing.EventSource+OverrideEventProvider
54 7fff35d38ef8      1         288 System.Collections.Generic.Dictionary<System.String, System.Object>+Entry[]
55 7fff35dbaa30      1         312 System.Globalization.NumberFormatInfo
56 7fff35d62208      1         400 System.Diagnostics.Tracing.RuntimeEventSource
57 7fff35dbc978      2         912 System.Globalization.CultureData
58 7fff35d3fa88     17         976 System.String[]
59 7fff35c99df8      8       3,432 System.Int32[]
60 7fff35bec4d8      3      16,544 System.Object[]
61 7fff35d9f920      3      33,356 System.Char[]
62 7fff35c9ec08    187      39,714 System.String
63 7fff35d69820      1     160,024 System.Runtime.InteropServices.GCHandle[](這是我們宣告的控制代碼陣列:pinnedHandles)
64 7fff35d69610      2     160,048 System.Byte[][](這是我們宣告的位元組陣列:nonPinned 和 pinned)
65 01e7e9acc5b0  9,846 492,996,800 Free
66 7fff35d69410 10,004 500,294,202 System.Byte[]
67 Total 20,148 objects, 993,711,066 bytes

                  此時,我們再看看【Free】塊,現在數量有 9846,總大小四號 492996800 位元組,也就是 492 MB,我們再使用【!eeheap -gc】找到 託管堆的大小。

 1 0:001> !eeheap -gc
 2 
 3 ========================================
 4 Number of GC Heaps: 1
 5 ----------------------------------------
 6 Small object heap
 7          segment            begin        allocated        committed allocated size     committed size    
 8 generation 0:
 9     0227ff4ad3f0     01e83fc00028     01e83fc00028     01e83fc01000                    0x1000 (4096)     
10 generation 1:
11     0227ff4ad340     01e83f800028     01e83f800028     01e83f801000                    0x1000 (4096)     
12 generation 2:
13     0227ff49f1c0     01e7ed800028     01e7edbf6290     01e7edc00000 0x3f6268 (4153960) 0x400000 (4194304)
14     0227ff49f270     01e7edc00028     01e7edc470f8     01e7edc68000 0x470d0 (291024)   0x68000 (425984)  
15     0227ff49f320     01e7ee000028     01e7ee178c28     01e7ee199000 0x178c00 (1543168) 0x199000 (1675264)
16    。。。。。。(省略了)
17     0227ff4ad080     01e83e800028     01e83ebf6290     01e83ec00000 0x3f6268 (4153960) 0x400000 (4194304)
18     0227ff4ad130     01e83ec00028     01e83ee01310     01e83ee11000 0x2012e8 (2101992) 0x211000 (2166784)
19     0227ff4ad1e0     01e83f000028     01e83f31a390     01e83f321000 0x31a368 (3253096) 0x321000 (3280896)
20 NonGC heap
21          segment            begin        allocated        committed allocated size     committed size    
22     01e7e9aa4100     02287ffa0008     02287ffa1d58     02287ffb0000 0x1d50 (7504)      0x10000 (65536)   
23 Large object heap
24          segment            begin        allocated        committed allocated size     committed size    
25     0227ff49f3d0     01e7ee400028     01e7ee427160     01e7ee428000 0x27138 (160056)   0x28000 (163840)  
26 Pinned object heap
27          segment            begin        allocated        committed allocated size     committed size    
28     0227ff49ec40     01e7eb800028     01e7eb804018     01e7eb811000 0x3ff0 (16368)     0x11000 (69632)   
29 ------------------------------
30 GC Allocated Heap Size:    Size: 0x3b3ad610 (993711632) bytes.
31 GC Committed Heap Size:    Size: 0x3d26e000 (1025957888) bytes.

                  我們看到 GC 堆總大小是 993 MB,空閒塊總大小是 也就是 492 MB,可以認為堆中 50% 的碎片。可以使用【!DumpHeap】命令確認這一點。

  1 0:001> !DumpHeap
  2          Address               MT           Size
  3     01e7eb800028     7fff35bec4d8          8,184 
  4     01e7eb802020     7fff35bec4d8          8,184 
  5     01e7ed800028     7fff35d69410         50,024 
  6     01e7ed80c390     01e7e9acc5b0         50,072 Free
  7     01e7ed818728     7fff35d69410         50,024 
  8     01e7ed824a90     01e7e9acc5b0         50,072 Free
  9     01e7ed830e28     7fff35d69410         50,024 
 10     01e7ed83d190     01e7e9acc5b0         50,072 Free
 11     01e7ed849528     7fff35d69410         50,024 
 12     01e7ed855890     01e7e9acc5b0         50,072 Free
 13     01e7ed861c28     7fff35d69410         50,024 
 14     01e7ed86df90     01e7e9acc5b0         50,072 Free
 15     01e7ed87a328     7fff35d69410         50,024 
 16     01e7ed886690     01e7e9acc5b0         50,072 Free
 17     01e7ed892a28     7fff35d69410         50,024 
 18     。。。。。。(省略了)
 19     01e83edd0510     01e7e9acc5b0         50,072 Free
 20     01e83eddc8a8     7fff35d69410         50,024 
 21     01e83ede8c10     01e7e9acc5b0         50,072 Free
 22     01e83edf4fa8     7fff35d69410         50,024 
 23     01e83f000028     7fff35d69410         50,024 
 24     01e83f00c390     01e7e9acc5b0         50,072 Free
 25     01e83f018728     7fff35d69410         50,024 
 26     01e83f024a90     01e7e9acc5b0         50,072 Free
 27     01e83f030e28     7fff35d69410         50,024 
 28     01e83f03d190     01e7e9acc5b0         50,072 Free
 29     01e83f049528     7fff35d69410         50,024 
 30     01e83f055890     01e7e9acc5b0         50,072 Free
 31     01e83f061c28     7fff35d69410         50,024 
 32     01e83f06df90     01e7e9acc5b0         50,072 Free
 33     01e83f07a328     7fff35d69410         50,024 
 34     01e83f086690     01e7e9acc5b0         50,072 Free
 35     01e83f092a28     7fff35d69410         50,024 
 36     01e83f09ed90     01e7e9acc5b0         50,072 Free
 37     01e83f0ab128     7fff35d69410         50,024 
 38     01e83f0b7490     01e7e9acc5b0         50,072 Free
 39     01e83f0c3828     7fff35d69410         50,024 
 40     01e83f0cfb90     01e7e9acc5b0         50,072 Free
 41     01e83f0dbf28     7fff35d69410         50,024 
 42     01e83f0e8290     01e7e9acc5b0         50,072 Free
 43     01e83f0f4628     7fff35d69410         50,024 
 44     01e83f100990     01e7e9acc5b0         50,072 Free
 45     01e83f10cd28     7fff35d69410         50,024 
 46     01e83f119090     01e7e9acc5b0         50,072 Free
 47     01e83f125428     7fff35d69410         50,024 
 48     01e83f131790     01e7e9acc5b0         50,072 Free
 49     01e83f13db28     7fff35d69410         50,024 
 50     01e83f149e90     01e7e9acc5b0         50,072 Free
 51     01e83f156228     7fff35d69410         50,024 
 52     01e83f162590     01e7e9acc5b0         50,072 Free
 53     01e83f16e928     7fff35d69410         50,024 
 54     01e83f17ac90     01e7e9acc5b0         50,072 Free
 55     01e83f187028     7fff35d69410         50,024 
 56     01e83f193390     01e7e9acc5b0         50,072 Free
 57     01e83f19f728     7fff35d69410         50,024 
 58     01e83f1aba90     01e7e9acc5b0         50,072 Free
 59     01e83f1b7e28     7fff35d69410         50,024 
 60     01e83f1c4190     01e7e9acc5b0         50,072 Free
 61     01e83f1d0528     7fff35d69410         50,024 
 62     01e83f1dc890     01e7e9acc5b0         50,072 Free
 63     01e83f1e8c28     7fff35d69410         50,024 
 64     01e83f1f4f90     01e7e9acc5b0         50,072 Free
 65     01e83f201328     7fff35d69410         50,024 
 66     01e83f20d690     01e7e9acc5b0         50,072 Free
 67     01e83f219a28     7fff35d69410         50,024 
 68     01e83f225d90     01e7e9acc5b0         50,072 Free
 69     01e83f232128     7fff35d69410         50,024 
 70     01e83f23e490     01e7e9acc5b0         50,072 Free
 71     01e83f24a828     7fff35d69410         50,024 
 72     01e83f256b90     01e7e9acc5b0         50,072 Free
 73     01e83f262f28     7fff35d69410         50,024 
 74     01e83f26f290     01e7e9acc5b0         50,072 Free
 75     01e83f27b628     7fff35d69410         50,024 
 76     01e83f287990     01e7e9acc5b0         50,072 Free
 77     01e83f293d28     7fff35d69410         50,024 
 78     01e83f2a0090     01e7e9acc5b0         50,072 Free
 79     01e83f2ac428     7fff35d69410         50,024 
 80     01e83f2b8790     01e7e9acc5b0         50,072 Free
 81     01e83f2c4b28     7fff35d69410         50,024 
 82     01e83f2d0e90     01e7e9acc5b0         50,072 Free
 83     01e83f2dd228     7fff35d69410         50,024 
 84     01e83f2e9590     01e7e9acc5b0         50,072 Free
 85     01e83f2f5928     7fff35d69410         50,024 
 86     01e83f301c90     01e7e9acc5b0         50,072 Free
 87     01e83f30e028     7fff35d69410         50,024 
 88     02287ffa0008     7fff35c9ec08             24 
 89     02287ffa0020     7fff35bea318             40 
。。。。。。(省略了)
237     02287ffa1c50     7fff35c9ec08             24 
238     02287ffa1c68     7fff35c9ec08             24 
239     02287ffa1c80     7fff35c9ec08             24 
240     02287ffa1c98     7fff35c9ec08             62 
241     02287ffa1cd8     7fff35c9ec08             50 
242     02287ffa1d10     7fff35d6eea0             40 
243     02287ffa1d38     7fff35c9ec08             28 
244 
245 Statistics:
246           MT  Count   TotalSize Class Name
247 7fff35d3ad28      1          24 System.Collections.Generic.GenericEqualityComparer<System.String>
248 7fff35d3c7d0      1          24 System.OrdinalCaseSensitiveComparer
249 7fff35d3c108      1          24 System.Collections.Generic.NonRandomizedStringEqualityComparer+OrdinalIgnoreCaseComparer
250 7fff35d3ca50      1          24 System.OrdinalIgnoreCaseComparer
251 7fff35d60100      1          24 ExampleCore_5_9.Program
252 7fff35d9a898      1          24 System.IO.Stream+NullStream
253 7fff35d9f1c0      1          24 System.Threading.Tasks.Task+<>c
254 7fff35db6dd0      1          24 System.IO.SyncTextReader
255 7fff35dbcb00      1          26 System.Globalization.CalendarId[]
256 7fff35d63038      1          32 System.Diagnostics.Tracing.ActivityTracker
257 7fff35d66300      1          32 System.Collections.Generic.List<System.WeakReference<System.Diagnostics.Tracing.EventSource>>
258 7fff35d61818      1          32 System.Guid
259 7fff35d671e0      1          40 System.WeakReference<System.Diagnostics.Tracing.EventSource>[]
260 7fff35d9de68      1          40 System.Threading.Tasks.TaskFactory
261 7fff35d9f5e0      1          40 System.IO.TextWriter+NullTextWriter
262 7fff35d6eea0      1          40 Interop+INPUT_RECORD
263 7fff35d3bfc8      2          48 System.Collections.Generic.NonRandomizedStringEqualityComparer+OrdinalComparer
264 7fff35d62338      2          48 System.Diagnostics.Tracing.TraceLoggingEventHandleTable
265 7fff35d656f8      2          48 System.WeakReference<System.Diagnostics.Tracing.EventSource>
266 7fff35d91428      1          48 System.Reflection.RuntimeAssembly
267 7fff35d93040      1          48 System.Text.UTF8Encoding+UTF8EncodingSealed
268 7fff35d93658      2          48 System.Text.EncoderReplacementFallback
269 7fff35d933d0      2          48 System.Text.DecoderReplacementFallback
270 7fff35db20f8      1          48 System.Text.OSEncoder
271 7fff35db4278      1          48 System.IO.TextWriter+SyncTextWriter
272 7fff35d9ad40      1          56 System.Text.ConsoleEncoding
273 7fff35db6b50      1          56 System.Text.DecoderDBCS
274 7fff35d9cfd0      1          64 System.Threading.ContextCallback
275 7fff35d9edb8      1          72 System.Threading.Tasks.Task<System.Threading.Tasks.VoidTaskResult>
276 7fff35dbd750      1          80 System.Collections.Generic.Dictionary<System.String, System.Globalization.CultureData>
277 7fff35d347c8      1          80 System.Collections.Generic.Dictionary<System.String, System.Object>
278 7fff35bea318      2          80 System.RuntimeType
279 7fff35d63d00      4          96 System.WeakReference<System.Diagnostics.Tracing.EventProvider>
280 7fff35d94378      2          96 System.ConsolePal+WindowsConsoleStream
281 7fff35db4e08      1          96 System.IO.StreamReader
282 7fff35dbf910      1          96 System.Collections.Generic.Dictionary<System.String, System.Globalization.CultureData>+Entry[]
283 7fff35d9a3c8      1         104 System.IO.StreamWriter
284 7fff35be5fa8      5         120 System.Object
285 7fff35d30a90      1         128 System.OutOfMemoryException
286 7fff35d30b90      1         128 System.StackOverflowException
287 7fff35d30c90      1         128 System.ExecutionEngineException
288 7fff35d637b8      2         128 System.Diagnostics.Tracing.EventPipeEventProvider
289 7fff35d91dd8      2         128 System.Text.OSEncoding
290 7fff35dbd370      1         160 System.Globalization.CalendarData
291 7fff35d63a20      2         176 System.Diagnostics.Tracing.EtwEventProvider
292 7fff35d685d0      1         184 System.Diagnostics.Tracing.NativeRuntimeEventSource
293 7fff35d62410      2         208 System.IntPtr[]
294 7fff35dbd3f0      1         208 System.Globalization.CalendarData[]
295 7fff35dbb3a8      2         224 System.Globalization.CultureInfo
296 7fff35d62b28      4         256 System.Diagnostics.Tracing.EventSource+OverrideEventProvider
297 7fff35d38ef8      1         288 System.Collections.Generic.Dictionary<System.String, System.Object>+Entry[]
298 7fff35dbaa30      1         312 System.Globalization.NumberFormatInfo
299 7fff35d62208      1         400 System.Diagnostics.Tracing.RuntimeEventSource
300 7fff35dbc978      2         912 System.Globalization.CultureData
301 7fff35d3fa88     17         976 System.String[]
302 7fff35c99df8      8       3,432 System.Int32[]
303 7fff35bec4d8      3      16,544 System.Object[]
304 7fff35d9f920      3      33,356 System.Char[]
305 7fff35c9ec08    187      39,714 System.String
306 7fff35d69820      1     160,024 System.Runtime.InteropServices.GCHandle[]
307 7fff35d69610      2     160,048 System.Byte[][]
308 01e7e9acc5b0  9,846 492,996,800 Free
309 7fff35d69410 10,004 500,294,202 System.Byte[]
310 Total 20,148 objects, 993,711,066 bytes

                  紅色標註的可以很清楚的看到,分配一個物件,空閒一個物件,交替出現。我們知道GC執行垃圾收集的時候會緊縮和合並,但是這裡並沒有執行緊縮和合並的操作,原因之一就是在堆上存在一些被固定住(不能移動的)的物件。

                  為了確定是否是有些物件被固定了,我們可以使用【!GCHandles】命令來看看程序中是否包含了“Pinned”型別的控制代碼。

 1 0:001> !GCHandles
 2           Handle Type                  Object     Size             Data Type
 3 000001E7E99D11B8 WeakShort   000001e7edc08f50      400                  System.Diagnostics.Tracing.RuntimeEventSource
 4 000001E7E99D11C0 WeakShort   000001e7edc09310      184                  System.Diagnostics.Tracing.NativeRuntimeEventSource
 5 000001E7E99D11C8 WeakShort   000001e7edc094f8       64                  System.Diagnostics.Tracing.EventSource+OverrideEventProvider
 6 000001E7E99D11D0 WeakShort   000001e7edc09448       64                  System.Diagnostics.Tracing.EventSource+OverrideEventProvider
 7 000001E7E99D11D8 WeakShort   000001e7edc09230       64                  System.Diagnostics.Tracing.EventSource+OverrideEventProvider
 8 000001E7E99D11E0 WeakShort   000001e7edc09180       64                  System.Diagnostics.Tracing.EventSource+OverrideEventProvider
 9 000001E7E99D1390 Strong      000001e7edc134c8      176                  System.Object[]
10 000001E7E99D1398 Strong      000001e7eb802020     8184                  System.Object[]
11 000001E7E99D13A0 Strong      000001e7edc09270       64                  System.Diagnostics.Tracing.EventPipeEventProvider
12 000001E7E99D13A8 Strong      000001e7edc09538       64                  System.Diagnostics.Tracing.EventPipeEventProvider
13 000001E7E99D13B0 Strong      000001e7edc09488       88                  System.Diagnostics.Tracing.EtwEventProvider
14 000001E7E99D13B8 Strong      000001e7edc091c0       88                  System.Diagnostics.Tracing.EtwEventProvider
15 000001E7E99D13C8 Strong      000001e7edc00188      128                  System.ExecutionEngineException
16 000001E7E99D13D0 Strong      000001e7edc00108      128                  System.StackOverflowException
17 000001E7E99D13D8 Strong      000001e7edc00088      128                  System.OutOfMemoryException
18 000001E7E99D13E0 Strong      000001e7edc00028       96                  System.Int32[]
19 000001E7E99D13E8 Strong      000001e7eb800028     8184                  System.Object[]
20 000001E7E99D1400 Pinned      000001e7f08b74a8    50024                  System.Byte[]
21 000001E7E99D1408 Pinned      000001e7f089eda8    50024                  System.Byte[]
22 。。。。。。(省略了)
23 000001E7E99D1490 Pinned      000001e7f06f5928    50024                  System.Byte[]
24 000001E7E99D1498 Pinned      000001e7f06dd228    50024                  System.Byte[]
25 000001E7E99D14A0 Pinned      000001e7f06c4b28    50024                  System.Byte[]
26 000001E7E99D14A8 Pinned      000001e7f06ac428    50024                  System.Byte[]
27 000001E7E99D14B0 Pinned      000001e7f0693d28    50024                  System.Byte[]
28 000001E7E99D14B8 Pinned      000001e7f067b628    50024                  System.Byte[]
29 000001E7E99D14C0 Pinned      000001e7f0662f28    50024                  System.Byte[]
30 000001E7E99D14C8 Pinned      000001e7f064a828    50024                  System.Byte[]
31 000001E7E99D14D0 Pinned      000001e7f0632128    50024                  System.Byte[]
32 000001E7E99D14D8 Pinned      000001e7f0619a28    50024                  System.Byte[]
33 000001E7E99D14E0 Pinned      000001e7f0601328    50024                  System.Byte[]
34 。。。。。。(省略了)
35 000001E7E99D4F70 Pinned      000001e7feb0e028    50024                  System.Byte[]
36 000001E7E99D4F78 Pinned      000001e7feaf5928    50024                  System.Byte[]
37 000001E7E99D4F80 Pinned      000001e7feadd228    50024                  System.Byte[]
38 000001E7E99D4F88 Pinned      000001e7feac4b28    50024                  System.Byte[]
39 000001E7E99D4F90 Pinned      000001e7feaac428    50024                  System.Byte[]
40 000001E7E99D4F98 Pinned      000001e7fea93d28    50024                  System.Byte[]
41 000001E7E99D4FA0 Pinned      000001e7fea7b628    50024                  System.Byte[]
42 000001E7E99D4FA8 Pinned      000001e7fea62f28    50024                  System.Byte[]
43 。。。。。。(省略了)
44 000001E7E99D8EC0 Pinned      000001e80f232128    50024                  System.Byte[]
45 000001E7E99D8EC8 Pinned      000001e80f219a28    50024                  System.Byte[]
46 000001E7E99D8ED0 Pinned      000001e80f201328    50024                  System.Byte[]
47 000001E7E99D8ED8 Pinned      000001e80f1e8c28    50024                  System.Byte[]
48 000001E7E99D8EE0 Pinned      000001e80f1d0528    50024                  System.Byte[]
49 000001E7E99D8EE8 Pinned      000001e80f1b7e28    50024                  System.Byte[]
50 000001E7E99D8EF0 Pinned      000001e80f19f728    50024                  System.Byte[]
51 000001E7E99D8EF8 Pinned      000001e80f187028    50024                  System.Byte[]
52 。。。。。。(省略了)
53 000001E7EB445DB8 Pinned      000001e83f24a828    50024                  System.Byte[]
54 000001E7EB445DC0 Pinned      000001e83f232128    50024                  System.Byte[]
55 000001E7EB445DC8 Pinned      000001e83f219a28    50024                  System.Byte[]
56 000001E7EB445DD0 Pinned      000001e83f201328    50024                  System.Byte[]
57 000001E7EB445DD8 Pinned      000001e83f1e8c28    50024                  System.Byte[]
58 000001E7EB445DE0 Pinned      000001e83f1d0528    50024                  System.Byte[]
59 000001E7EB445DE8 Pinned      000001e83f1b7e28    50024                  System.Byte[]
60 000001E7EB445DF0 Pinned      000001e83f19f728    50024                  System.Byte[]
61 000001E7EB445DF8 Pinned      000001e83f187028    50024                  System.Byte[]
62 
63 Statistics:
64               MT    Count    TotalSize Class Name
65 00007fff35be5fa8        1           24 System.Object
66 00007fff35c99df8        1           96 System.Int32[]
67 00007fff35d637b8        2          128 System.Diagnostics.Tracing.EventPipeEventProvider
68 00007fff35d30c90        1          128 System.ExecutionEngineException
69 00007fff35d30b90        1          128 System.StackOverflowException
70 00007fff35d30a90        1          128 System.OutOfMemoryException
71 00007fff35d63a20        2          176 System.Diagnostics.Tracing.EtwEventProvider
72 00007fff35d685d0        1          184 System.Diagnostics.Tracing.NativeRuntimeEventSource
73 00007fff35d62b28        4          256 System.Diagnostics.Tracing.EventSource+OverrideEventProvider
74 00007fff35d62208        1          400 System.Diagnostics.Tracing.RuntimeEventSource
75 00007fff35bec4d8        3        16544 System.Object[]
76 00007fff35d69410    10000    500240000 System.Byte[](有 10000 個用於固定位元組陣列)
77 Total 10018 objects
78 
79 Handles:
80     Strong Handles:       11
81     Pinned Handles:       10001(Pinned 控制代碼有這麼多)
82     Weak Short Handles:   6

                  程序中有 10001 個型別為“Pinned”的控制代碼,有 10000 個這樣的控制代碼用於固定位元組陣列。


            B2、除錯原始碼:ExampleCore_5_10
               除錯任務:如何除錯 .NET 程式的記憶體洩漏            
              1)、NTSD 除錯
                  編譯專案,首先,把我們的控制檯程式執行起來,然後再開啟【Visual Studio 2022 Developer Command Prompt v17.9.6】命令列工具,輸入命令【NTSD -pn ExampleCore_5_10.exe】開啟偵錯程式。效果如圖:
                  

                  此時,偵錯程式中斷執行,如圖:
                  

                  在看看我們的控制檯程式,執行情況如圖:
                  

                  準備就緒,開始我們的除錯,先看看 GC 堆的情況,執行【!eeheap -gc】命令。

 1 0:007> !eeheap -gc
 2 Number of GC Heaps: 1
 3 generation 0 starts at 0x00000206CB400028
 4 generation 1 starts at 0x00000206CB000028
 5 generation 2 starts at 0x000002475D5C0008
 6 ephemeral segment allocation context: none
 7          segment             begin         allocated         committed    allocated size    committed size
 8 generation 0:
 9 00000246DCABF320  00000206CB400028  00000206CB40E070  00000206CB761000  0xe048(57416)  0x360fd8(3543000)
10 generation 1:
11 00000246DCABF270  00000206CB000028  00000206CB015378  00000206CB051000  0x15350(86864)  0x50fd8(331736)
12 generation 2:
13 00000206C6E61A60  000002475D5C0008  000002475D627D48  000002475D630000  0x67d40(425280)  0x6fff8(458744)
14 00000246DCABF1C0  00000206CAC00028  00000206CAC588D0  00000206CAC61000  0x588a8(362664)  0x60fd8(397272)
15 Large object heap starts at 0x0000000000000000
16          segment             begin         allocated         committed    allocated size    committed size
17 00000246DCABF3D0  00000206CB800028  00000206CB800028  00000206CB801000  0x0(0)  0xfd8(4056)
18 Pinned object heap starts at 0x0000000000000000
19 00000246DCABEC40  00000206C8C00028  00000206C8C13F60  00000206C8C21000  0x13f38(81720)  0x20fd8(135128)
20 Total Allocated Size:              Size: 0xf78b8 (1013944) bytes.
21 Total Committed Size:              Size: 0x483f58 (4734808) bytes.
22 ------------------------------
23 GC Allocated Heap Size:    Size: 0xf78b8 (1013944) bytes.
24 GC Committed Heap Size:    Size: 0x483f58 (4734808) bytes.

                  我看完了,感覺沒啥異常情況,在看看我們的引用程式域有沒有什麼問題,執行命令【!DumpDomain】。

 1 0:007> !DumpDomain
 2 。。。。。。(無關緊要的省略了)
 3 Assembly:           000002475f74abd0 (Dynamic) []
 4 ClassLoader:        000002475D9EF470
 5   Module
 6   00007ff911a52b20    Dynamic Module
 7 
 8 Assembly:           000002475f74a4b0 (Dynamic) []
 9 ClassLoader:        000002475D9EFC00
10   Module
11   00007ff911a535e8    Dynamic Module
12 
13 Assembly:           000002475f74b470 (Dynamic) []
14 ClassLoader:        000002475D9EF7E0
15   Module
16   00007ff911a540b0    Dynamic Module
17 
18 Assembly:           000002475f74a870 (Dynamic) []
19 ClassLoader:        000002475D9EF5D0
20   Module
21   00007ff911a54b78    Dynamic Module
22 
23 Assembly:           000002475f74c010 (Dynamic) []
24 ClassLoader:        000002475D9EEEF0
25   Module
26   00007ff911a55640    Dynamic Module
27 
28 。。。。。。(太多了,省略了)
29 
30 Assembly:           0000024760ec5970 (Dynamic) []
31 ClassLoader:        0000024760F1F850
32   Module
33   00007ff9124ddd38    Dynamic Module
34 
35 Assembly:           0000024760ec6330 (Dynamic) []
36 ClassLoader:        0000024760F1F430
37   Module
38   00007ff9124de800    Dynamic Module
39 
40 Assembly:           0000024760ec5c70 (Dynamic) []
41 ClassLoader:        0000024760F1E670
42   Module
43   00007ff9124df2c8    Dynamic Module
44 
45 Assembly:           0000024760ec5df0 (Dynamic) []
46 ClassLoader:        0000024760F1F9B0
47   Module
48   00007ff912530000    Dynamic Module
49 
50 Assembly:           0000024760ec6090 (Dynamic) []
51 ClassLoader:        0000024760F1DE30
52   Module
53   00007ff912530ac8    Dynamic Module
54 
55 Assembly:           0000024760ec8490 (Dynamic) []
56 ClassLoader:        0000024760F1DB70
57   Module
58   00007ff912531590    Dynamic Module
59 
60 。。。。。。(太多了,省略了)
61 
62 Assembly:           000002476352caa0 (Dynamic) []
63 ClassLoader:        0000024762C391C0
64   Module
65   00007ff91379d270    Dynamic Module
66 
67 Assembly:           000002476352c9e0 (Dynamic) []
68 ClassLoader:        0000024762C39530
69   Module
70   00007ff91379dd38    Dynamic Module
71 
72 Assembly:           000002476352db80 (Dynamic) []
73 ClassLoader:        0000024762C39C10
74   Module
75   00007ff91379e800    Dynamic Module
76 
77 Assembly:           000002476352f680 (Dynamic) []
78 ClassLoader:        0000024762C39950
79   Module
80   00007ff91379f2c8    Dynamic Module
81 
82 Assembly:           000002476352efc0 (Dynamic) []
83 ClassLoader:        0000024762C39CC0
84   Module
85   00007ff913800000    Dynamic Module
86 
87 。。。。。。(省略了)
88 
89 Assembly:           0000024763a92070 (Dynamic) []
90 ClassLoader:        0000024763A30020
91   Module
92   00007ff913982df0    Dynamic Module
93 
94 0:007>

                  在【Domain 1】應用程式域中,載入了很多動態的程式集,太多了,不用過腦子,都知道有問題。
                  接下來,我我們看看這個模組有什麼特別之處,執行命令【!DumpModule 00007ff913982df0】,我們就隨機的選擇最後一個程式集。

 1 0:007> !DumpModule 00007ff913982df0
 2 Name: Unknown Module
 3 Attributes:              Reflection IsDynamic IsInMemory
 4 TransientFlags:          00200811
 5 Assembly:                0000024763a92070
 6 BaseAddress:             0000000000000000
 7 PEFile:                  00000247633187B0
 8 ModuleId:                00007FF9139831F0
 9 ModuleIndex:             000000000000076D
10 LoaderHeap:              00007FF971464588
11 TypeDefToMethodTableMap: 00007FF91397E100
12 TypeRefToMethodTableMap: 00007FF91397E128
13 MethodDefToDescMap:      00007FF91397E1A0
14 FieldDefToDescMap:       00007FF91397E1F0
15 MemberRefToDescMap:      00007FF91397E150
16 FileReferencesMap:       0000000000000000
17 AssemblyReferencesMap:   00007FF91397E290

                  特性一行是我們特別要關注的,Reflection 表示是反射的,IsDynamic 表示是動態建立的(不是程式設計師搞的),IsInMemory 直接在記憶體中的,3個特性就是告訴我們,這個模組是透過反射技術、由系統創在記憶體中直接建立的。

                  它既然有執行呼叫,肯定有呼叫棧,呼叫棧肯定是有我們的程式觸發的,所以我們,必須切換到託管執行緒上下文,檢視具體的呼叫棧。先執行【~0s】切換執行緒,然後再執行【!clrstack】命令,檢視具體的呼叫棧。

1 0:007> ~0s
2 00007ff9`115d11f3 488b4050        mov     rax,qword ptr [rax+50h] ds:00007ff9`116fe810=00007ff9116fe310

                  繼續執行。            

 1 0:000> !clrstack
 2 OS Thread Id: 0x2420 (0)
 3         Child SP               IP Call Site
 4 0000009BDD97E290 00007ff980bac400 [InlinedCallFrame: 0000009bdd97e290]
 5 0000009BDD97E290 00007ff9735a4500 [InlinedCallFrame: 0000009bdd97e290]
 6 0000009BDD97E260 00007FF9735A4500 System.Reflection.Emit.RuntimeModuleBuilder.GetTypeRefNested(System.Type, System.Reflection.Module)
 7 0000009BDD97E380 00007FF9735A6529 System.Reflection.Emit.RuntimeModuleBuilder.GetTypeTokenWorkerNoLock(System.Type, Boolean)
 8 0000009BDD97E480 00007FF9735A62BF System.Reflection.Emit.RuntimeModuleBuilder.GetTypeTokenInternal(System.Type, Boolean)
 9 0000009BDD97E4E0 00007FF9735A67F4 System.Reflection.Emit.RuntimeModuleBuilder.GetMethodTokenNoLock(System.Reflection.MethodInfo, Boolean)
10 0000009BDD97E590 00007FF9735A669A System.Reflection.Emit.RuntimeModuleBuilder.GetMethodMetadataToken(System.Reflection.MethodInfo)
11 0000009BDD97E5F0 00007FF9735A6E68 System.Reflection.Emit.RuntimeModuleBuilder.GetMethodTokenInternal(System.Reflection.MethodBase, System.Type[], Boolean)
12 0000009BDD97E680 00007FF97359FD59 System.Reflection.Emit.RuntimeILGenerator.EmitCall(System.Reflection.Emit.OpCode, System.Reflection.MethodInfo, System.Type[])
13 0000009BDD97E700 00007FF97359F724 System.Reflection.Emit.RuntimeILGenerator.Emit(System.Reflection.Emit.OpCode, System.Reflection.MethodInfo)
14 0000009BDD97E750 00007FF972DA68C3 System.Xml.Serialization.XmlSerializationILGen.GenerateTypedSerializer(System.String, System.String, System.Xml.Serialization.XmlMapping, System.Xml.Serialization.CodeIdentifiers, System.String, System.String, System.String)
15 0000009BDD97E800 00007FF972D6BD5D System.Xml.Serialization.TempAssembly.GenerateRefEmitAssembly(System.Xml.Serialization.XmlMapping[], System.Type[])
16 0000009BDD97E970 00007FF972D6AC4C System.Xml.Serialization.TempAssembly..ctor(System.Xml.Serialization.XmlMapping[], System.Type[], System.String, System.String)
17 0000009BDD97E9E0 00007FF972DE4DCD System.Xml.Serialization.XmlSerializer.GenerateTempAssembly(System.Xml.Serialization.XmlMapping, System.Type, System.String, System.String)
18 0000009BDD97EA40 00007FF972DE4673 System.Xml.Serialization.XmlSerializer..ctor(System.Type, System.Xml.Serialization.XmlRootAttribute)
19 0000009BDD97EA90 00007FF921171B74 ExampleCore_5_10.Program.Run()
20 0000009BDD97EB50 00007FF921171988 ExampleCore_5_10.Program.Main(System.String[])

                  到這裡,我們就很清楚了, System.Xml.Serialization.XmlSerializer..ctor 型別建立了例項,在內部又呼叫了 System.Xml.Serialization.XmlSerializer.GenerateTempAssembly 建立臨時的程式集,問題找到了,剩下的就簡單了。

              2)、Windbg Preview 除錯
                  編譯專案,執行我們的控制檯程式:ExampleCore_5_10.exe,等輸出的數字大於2000,輸出內容如:已經序列化第【XXXX】個人了,XXXX 就是具體的數字,開始開啟【Windbg Preview】偵錯程式,依次點選【檔案】---【Attach to process】,點選【Attach】附加我們的程序。此時偵錯程式卡死,我們的控制檯程式輸出也停止了,我們回到偵錯程式中,點選【Break】按鈕,就可以除錯我們的任務了。
                  此時的輸出的內容可能太多,我們可以使用【.cls】命令清理一下偵錯程式的輸出。
                  我們先檢視一下託管堆的情況,執行命令【!eeheap】。

 1 0:008> !eeheap
 2 Loader Heap:
 3 ----------------------------------------
 4 System Domain:        7ff96f6d40c0
 5 LoaderAllocator:      7ff96f6d40c0
 6 LowFrequencyHeap:     7ff914280000(10000:3000) 7ff914270000(10000:f000) 7ff914260000(10000:f000) 7ff914240000(10000:f000) 7ff914220000(10000:f000) 7ff914210000(10000:f000) 7ff914200000(10000:f000) 7ff9141f0000(10000:f000) 7ff9141e0000(10000:f000) 7ff9141c0000(10000:f000) 7ff9141b0000(10000:f000) 7ff9141a0000(10000:f000) 7ff914190000(10000:f000) 7ff914170000(10000:f000) 7ff914150000(10000:f000) 7ff914140000(10000:f000) 7ff914130000(10000:f000) 7ff914120000(10000:f000) 7ff914100000(10000:f000) 7ff9140f0000(10000:f000) 7ff9140e0000(10000:f000) 7ff9140d0000(10000:f000) 7ff9140c0000(10000:f000) 7ff9140a0000(10000:f000) 7ff914090000(10000:f000) 7ff914080000(10000:f000) 7ff914060000(10000:f000) 7ff914040000(10000:f000) 7ff914030000(10000:f000) 7ff914020000(10000:f000) 7ff914010000(10000:f000) 7ff914000000(10000:f000) 7ff913fe0000(10000:f000) 7ff913fd0000(10000:f000) 7ff913fc0000(10000:f000) 7ff913fb0000(10000:f000) 7ff913fa0000(10000:f000) 7ff913f70000(10000:f000) 7ff913f60000(10000:f000) 7ff913f50000(10000:f000) 7ff913f40000(10000:f000) 7ff913f30000(10000:f000) 7ff913f10000(10000:f000) 7ff913f00000(10000:f000) 7ff913ef0000(10000:f000) 7ff913ee0000(10000:f000) 7ff913ec0000(10000:f000) 7ff913eb0000(10000:f000) 7ff913e90000(10000:f000) 7ff913e80000(10000:f000) 7ff913e70000(10000:f000) 7ff913e50000(10000:f000) 7ff913e40000(10000:f000) 7ff913e30000(10000:f000) 7ff913e20000(10000:f000) 7ff913e10000(10000:f000) 7ff913df0000(10000:f000) 7ff913de0000(10000:f000) 7ff913dd0000(10000:f000) 7ff913dc0000(10000:f000) 7ff913da0000(10000:f000) 7ff913d80000(10000:f000) 7ff913d70000(10000:f000) 7ff913d60000(10000:f000) 7ff913d50000(10000:f000) 7ff913d30000(10000:f000) 7ff913d20000(10000:f000) 7ff913d10000(10000:f000) 7ff913d00000(10000:f000) 7ff913cf0000(10000:f000) 7ff913cd0000(10000:f000) 7ff913cb0000(10000:f000) 7ff913ca0000(10000:f000) 7ff913c90000(10000:f000) 7ff913c80000(10000:f000) 7ff913c60000(10000:f000) 7ff913c50000(10000:f000) 7ff913c40000(10000:f000) 7ff913c30000(10000:f000) 7ff913c20000(10000:f000) 7ff913c00000(10000:f000) 7ff913bf0000(10000:f000) 7ff913bd0000(10000:f000) 7ff913bc0000(10000:f000) 7ff913ba0000(10000:f000) 7ff913b90000(10000:f000) 7ff913b80000(10000:f000) 7ff913b70000(10000:f000) 7ff913b60000(10000:f000) 7ff913b40000(10000:f000) 7ff913b30000(10000:f000) 7ff913b20000(10000:f000) 7ff913b10000(10000:f000) 7ff913b00000(10000:f000) 7ff913ad0000(10000:f000) 7ff913ac0000(10000:f000) 7ff913ab0000(10000:f000) 7ff913aa0000(10000:f000) 7ff913a80000(10000:f000) 7ff913a70000(10000:f000) 7ff913a60000(10000:f000) 7ff913a50000(10000:f000) 7ff913a40000(10000:f000) 7ff913a20000(10000:f000) 7ff913a10000(10000:f000) 7ff9139f0000(10000:f000) 7ff9139e0000(10000:f000) 7ff9139d0000(10000:f000) 7ff9139b0000(10000:f000) 7ff9139a0000(10000:f000) 7ff913990000(10000:f000) 7ff913980000(10000:f000) 7ff913970000(10000:f000) 7ff913950000(10000:f000) 7ff913940000(10000:f000) 7ff913930000(10000:f000) 7ff913910000(10000:f000) 7ff9138f0000(10000:f000) 7ff9138e0000(10000:f000) 7ff9138d0000(10000:f000) 7ff9138c0000(10000:f000) 7ff9138b0000(10000:f000) 7ff913890000(10000:f000) 7ff913880000(10000:f000) 7ff913870000(10000:f000) 7ff913860000(10000:f000) 7ff913850000(10000:f000) 7ff913830000(10000:f000) 7ff913810000(10000:f000) 7ff913800000(10000:f000) 7ff9137f0000(10000:f000) 7ff9137e0000(10000:f000) 7ff9137c0000(10000:f000) 7ff9137b0000(10000:f000) 7ff9137a0000(10000:f000) 7ff913790000(10000:f000) 7ff913770000(10000:f000) 7ff913760000(10000:f000) 7ff913750000(10000:f000) 7ff913730000(10000:f000) 7ff913720000(10000:f000) 7ff913700000(10000:f000) 7ff9136f0000(10000:f000) 7ff9136e0000(10000:f000) 7ff9136d0000(10000:f000) 7ff9136c0000(10000:f000) 7ff9136a0000(10000:f000) 7ff913690000(10000:f000) 7ff913680000(10000:f000) 7ff913670000(10000:f000) 7ff913650000(10000:f000) 7ff913630000(10000:f000) 7ff913620000(10000:f000) 7ff913610000(10000:f000) 7ff913600000(10000:f000) 7ff9135e0000(10000:f000) 7ff9135d0000(10000:f000) 7ff9135c0000(10000:f000) 7ff9135b0000(10000:f000) 7ff9135a0000(10000:f000) 7ff913580000(10000:f000) 7ff913570000(10000:f000) 7ff913550000(10000:f000) 7ff913540000(10000:f000) 7ff913530000(10000:f000) 7ff913510000(10000:f000) 7ff913500000(10000:f000) 7ff9134f0000(10000:f000) 7ff9134e0000(10000:f000) 7ff9134c0000(10000:f000) 7ff9134b0000(10000:f000) 7ff9134a0000(10000:f000) 7ff913490000(10000:f000) 7ff913470000(10000:f000) 7ff913450000(10000:f000) 7ff913440000(10000:f000) 7ff913430000(10000:f000) 7ff913420000(10000:f000) 7ff913410000(10000:f000) 7ff9133f0000(10000:f000) 7ff9133e0000(10000:f000) 7ff9133d0000(10000:f000) 7ff9133c0000(10000:f000) 7ff9133b0000(10000:f000) 7ff913380000(10000:f000) 7ff913370000(10000:f000) 7ff913360000(10000:f000) 7ff913350000(10000:f000) 7ff913330000(10000:f000) 7ff913320000(10000:f000) 7ff913310000(10000:f000) 7ff913300000(10000:f000) 7ff9132f0000(10000:f000) 7ff9132d0000(10000:f000) 7ff9132c0000(10000:f000) 7ff9132b0000(10000:f000) 7ff913290000(10000:f000) 7ff913280000(10000:f000) 7ff913260000(10000:f000) 7ff913250000(10000:f000) 7ff913240000(10000:f000) 7ff913230000(10000:f000) 7ff913220000(10000:f000) 7ff913200000(10000:f000) 7ff9131f0000(10000:f000) 7ff9131e0000(10000:f000) 7ff9131d0000(10000:f000) 7ff9131a0000(10000:f000) 7ff913190000(10000:f000) 7ff913180000(10000:f000) 7ff913170000(10000:f000) 7ff913160000(10000:f000) 7ff913140000(10000:f000) 7ff913130000(10000:f000) 7ff913120000(10000:f000) 7ff913110000(10000:f000) 7ff913100000(10000:f000) 7ff9130e0000(10000:f000) 7ff9130c0000(10000:f000) 7ff9130b0000(10000:f000) 7ff9130a0000(10000:f000) 7ff913080000(10000:f000) 7ff913070000(10000:10000) 7ff912e60000(10000:e000) 7ff912e50000(10000:f000) 7ff912e40000(10000:f000) 7ff912e30000(10000:f000) 7ff912e10000(10000:f000) 7ff912e00000(10000:f000) 7ff912df0000(10000:f000) 7ff912dd0000(10000:f000) 7ff912db0000(10000:f000) 7ff912da0000(10000:f000) 7ff912d90000(10000:f000) 7ff912d80000(10000:f000) 7ff912d70000(10000:f000) 7ff912d50000(10000:f000) 7ff912d40000(10000:f000) 7ff912d30000(10000:f000) 7ff912d20000(10000:f000) 7ff912d10000(10000:f000) 7ff912ce0000(10000:f000) 7ff912cd0000(10000:f000) 7ff912cc0000(10000:f000) 7ff912cb0000(10000:f000) 7ff912ca0000(10000:f000) 7ff912c80000(10000:f000) 7ff912c70000(10000:f000) 7ff912c60000(10000:f000) 7ff912c50000(10000:f000) 7ff912c30000(10000:f000) 7ff912c20000(10000:f000) 7ff912c10000(10000:f000) 7ff912bf0000(10000:f000) 7ff912be0000(10000:f000) 7ff912bc0000(10000:f000) 7ff912bb0000(10000:f000) 7ff912ba0000(10000:f000) 7ff912b90000(10000:f000) 7ff912b80000(10000:f000) 7ff912b60000(10000:f000) 7ff912b50000(10000:f000) 7ff912b40000(10000:f000) 7ff912b30000(10000:f000) 7ff912b10000(10000:f000) 7ff912af0000(10000:f000) 7ff912ae0000(10000:f000) 7ff912ad0000(10000:f000) 7ff912ac0000(10000:f000) 7ff912aa0000(10000:f000) 7ff912a90000(10000:f000) 7ff912a80000(10000:f000) 7ff912a70000(10000:f000) 7ff912a60000(10000:f000) 7ff912a40000(10000:f000) 7ff912a20000(10000:f000) 7ff912a10000(10000:f000) 7ff912a00000(10000:f000) 7ff9129f0000(10000:f000) 7ff9129d0000(10000:f000) 7ff9129c0000(10000:f000) 7ff9129b0000(10000:f000) 7ff9129a0000(10000:f000) 7ff912980000(10000:f000) 7ff912970000(10000:f000) 7ff912960000(10000:f000) 7ff912950000(10000:f000) 7ff912930000(10000:f000) 7ff912910000(10000:f000) 7ff912900000(10000:f000) 7ff9128f0000(10000:f000) 7ff9128e0000(10000:f000) 7ff9128d0000(10000:f000) 7ff9128b0000(10000:f000) 7ff9128a0000(10000:f000) 7ff912890000(10000:f000) 7ff912880000(10000:f000) 7ff912870000(10000:f000) 7ff912840000(10000:f000) 7ff912830000(10000:f000) 7ff912820000(10000:f000) 7ff912810000(10000:f000) 7ff9127f0000(10000:f000) 7ff9127e0000(10000:f000) 7ff9127d0000(10000:f000) 7ff9127c0000(10000:f000) 7ff9127b0000(10000:f000) 7ff912790000(10000:f000) 7ff912780000(10000:f000) 7ff912760000(10000:f000) 7ff912750000(10000:f000) 7ff912740000(10000:f000) 7ff912720000(10000:f000) 7ff912710000(10000:f000) 7ff912700000(10000:f000) 7ff9126f0000(10000:f000) 7ff9126e0000(10000:f000) 7ff9126c0000(10000:f000) 7ff9126b0000(10000:f000) 7ff9126a0000(10000:f000) 7ff912690000(10000:f000) 7ff912660000(10000:f000) 7ff912650000(10000:f000) 7ff912640000(10000:f000) 7ff9125b0000(10000:10000) 7ff9125a0000(10000:f000) 7ff912580000(10000:f000) 7ff912570000(10000:f000) 7ff912560000(10000:f000) 7ff912550000(10000:f000) 7ff912540000(10000:f000) 7ff912520000(10000:f000) 7ff912500000(10000:f000) 7ff9124f0000(10000:f000) 7ff9124e0000(10000:f000) 7ff9124d0000(10000:f000) 7ff9124b0000(10000:f000) 7ff9124a0000(10000:f000) 7ff912490000(10000:f000) 7ff912480000(10000:f000) 7ff912460000(10000:f000) 7ff912450000(10000:f000) 7ff912440000(10000:f000) 7ff912420000(10000:f000) 7ff912410000(10000:f000) 7ff9123f0000(10000:f000) 7ff9123e0000(10000:f000) 7ff9123d0000(10000:f000) 7ff9123c0000(10000:f000) 7ff9123b0000(10000:f000) 7ff912390000(10000:f000) 7ff912380000(10000:f000) 7ff912370000(10000:f000) 7ff912360000(10000:f000) 7ff912340000(10000:f000) 7ff912320000(10000:f000) 7ff912310000(10000:f000) 7ff912300000(10000:f000) 7ff9122f0000(10000:f000) 7ff9122d0000(10000:f000) 7ff9122c0000(10000:f000) 7ff9122b0000(10000:f000) 7ff9122a0000(10000:f000) 7ff912290000(10000:f000) 7ff912270000(10000:f000) 7ff912260000(10000:f000) 7ff912240000(10000:f000) 7ff912230000(10000:f000) 7ff912220000(10000:f000) 7ff912200000(10000:f000) 7ff9121f0000(10000:f000) 7ff9121e0000(10000:f000) 7ff9121d0000(10000:f000) 7ff9121b0000(10000:f000) 7ff9121a0000(10000:f000) 7ff912190000(10000:f000) 7ff912180000(10000:f000) 7ff912160000(10000:f000) 7ff912140000(10000:f000) 7ff912130000(10000:f000) 7ff912120000(10000:f000) 7ff912110000(10000:f000) 7ff912100000(10000:f000) 7ff9120e0000(10000:f000) 7ff9120d0000(10000:f000) 7ff9120c0000(10000:f000) 7ff9120b0000(10000:f000) 7ff9120a0000(10000:f000) 7ff912080000(10000:f000) 7ff912060000(10000:f000) 7ff912050000(10000:f000) 7ff912040000(10000:f000) 7ff912020000(10000:f000) 7ff912010000(10000:f000) 7ff912000000(10000:f000) 7ff911ff0000(10000:f000) 7ff911fe0000(10000:f000) 7ff911fc0000(10000:f000) 7ff911fb0000(10000:f000) 7ff911fa0000(10000:f000) 7ff911f80000(10000:f000) 7ff911f70000(10000:f000) 7ff911f50000(10000:f000) 7ff911f40000(10000:f000) 7ff911f30000(10000:f000) 7ff911f20000(10000:f000) 7ff911f10000(10000:f000) 7ff911ef0000(10000:f000) 7ff911ee0000(10000:f000) 7ff911ed0000(10000:f000) 7ff911ec0000(10000:f000) 7ff911e90000(10000:f000) 7ff911e80000(10000:f000) 7ff911e70000(10000:f000) 7ff911e60000(10000:f000) 7ff911e50000(10000:f000) 7ff911e30000(10000:f000) 7ff911e20000(10000:f000) 7ff911e10000(10000:f000) 7ff911e00000(10000:f000) 7ff911df0000(10000:f000) 7ff911dd0000(10000:f000) 7ff911dc0000(10000:f000) 7ff911da0000(10000:f000) 7ff911d90000(10000:f000) 7ff911cf0000(10000:10000) 7ff911ce0000(10000:f000) 7ff911cd0000(10000:f000) 7ff911cc0000(10000:f000) 7ff911cb0000(10000:f000) 7ff911c90000(10000:f000) 7ff911c80000(10000:f000) 7ff911c70000(10000:f000) 7ff911c60000(10000:f000) 7ff911c40000(10000:f000) 7ff911c20000(10000:f000) 7ff911c10000(10000:f000) 7ff911c00000(10000:f000) 7ff911bf0000(10000:f000) 7ff911be0000(10000:f000) 7ff911bc0000(10000:f000) 7ff911bb0000(10000:f000) 7ff911ba0000(10000:f000) 7ff911b90000(10000:f000) 7ff911b70000(10000:f000) 7ff911b50000(10000:f000) 7ff911b40000(10000:f000) 7ff911b30000(10000:f000) 7ff911b20000(10000:f000) 7ff911b00000(10000:f000) 7ff911af0000(10000:f000) 7ff911ae0000(10000:f000) 7ff911ad0000(10000:f000) 7ff911ac0000(10000:f000) 7ff911aa0000(10000:f000) 7ff911a90000(10000:f000) 7ff911a80000(10000:f000) 7ff911a60000(10000:f000) 7ff911a50000(10000:f000) 7ff911a30000(10000:f000) 7ff911a20000(10000:f000) 7ff911a10000(10000:f000) 7ff911a00000(10000:f000) 7ff9119e0000(10000:f000) 7ff9119d0000(10000:f000) 7ff9119c0000(10000:f000) 7ff9119b0000(10000:f000) 7ff9119a0000(10000:f000) 7ff911970000(10000:f000) 7ff911960000(10000:f000) 7ff911950000(10000:f000) 7ff911940000(10000:f000) 7ff911930000(10000:f000) 7ff911910000(10000:f000) 7ff911900000(10000:f000) 7ff9118f0000(10000:f000) 7ff9118e0000(10000:f000) 7ff9118d0000(10000:f000) 7ff9118b0000(10000:f000) 7ff911890000(10000:f000) 7ff911880000(10000:f000) 7ff911870000(10000:f000) 7ff911850000(10000:f000) 7ff911840000(10000:f000) 7ff911830000(10000:f000) 7ff911820000(10000:f000) 7ff911810000(10000:f000) 7ff9117f0000(10000:f000) 7ff9117e0000(10000:f000) 7ff9117d0000(10000:f000) 7ff9117c0000(10000:f000) 7ff9117a0000(10000:f000) 7ff911780000(10000:f000) 7ff911770000(10000:f000) 7ff911760000(10000:f000) 7ff911750000(10000:f000) 7ff911730000(10000:f000) 7ff911720000(10000:f000) 7ff911710000(10000:f000) 7ff911700000(10000:f000) 7ff9116f0000(10000:f000) 7ff9116d0000(10000:f000) 7ff9116b0000(10000:f000) 7ff9116a0000(10000:f000) 7ff911690000(10000:f000) 7ff911680000(10000:f000) 7ff911660000(10000:f000) 7ff911650000(10000:f000) 7ff911640000(10000:f000) 7ff911630000(10000:f000) 7ff911620000(10000:f000) 7ff911600000(10000:f000) 7ff9115f0000(10000:f000) 7ff9115d0000(10000:f000) 7ff9115c0000(10000:f000) 7ff9115a0000(10000:f000) 7ff911590000(10000:f000) 7ff911580000(10000:f000) 7ff911570000(10000:f000) 7ff911560000(10000:f000) 7ff911540000(10000:f000) 7ff911530000(10000:f000) 7ff911520000(10000:f000) 7ff911510000(10000:f000) 7ff911500000(10000:f000) 7ff9114d0000(10000:f000) 7ff9114c0000(10000:f000) 7ff911430000(10000:10000) 7ff911420000(10000:f000) 7ff911410000(10000:f000) 7ff9113f0000(10000:f000) 7ff9113e0000(10000:f000) 7ff9113d0000(10000:f000) 7ff9113c0000(10000:f000) 7ff9113a0000(10000:f000) 7ff911390000(10000:f000) 7ff911370000(10000:f000) 7ff911360000(10000:f000) 7ff911350000(10000:f000) 7ff911330000(10000:f000) 7ff911320000(10000:f000) 7ff911310000(10000:f000) 7ff911300000(10000:f000) 7ff9112f0000(10000:f000) 7ff9112d0000(10000:f000) 7ff9112c0000(10000:f000) 7ff9112b0000(10000:f000) 7ff911290000(10000:f000) 7ff911280000(10000:f000) 7ff911260000(10000:f000) 7ff911250000(10000:f000) 7ff911240000(10000:f000) 7ff911230000(10000:f000) 7ff911210000(10000:f000) 7ff911200000(10000:f000) 7ff9111f0000(10000:f000) 7ff9111e0000(10000:f000) 7ff9111d0000(10000:f000) 7ff9111b0000(10000:f000) 7ff911190000(10000:f000) 7ff911180000(10000:f000) 7ff911170000(10000:f000) 7ff911160000(10000:f000) 7ff911140000(10000:f000) 7ff911130000(10000:f000) 7ff911120000(10000:f000) 7ff911110000(10000:f000) 7ff9110f0000(10000:f000) 7ff9110e0000(10000:f000) 7ff9110d0000(10000:f000) 7ff9110b0000(10000:f000) 7ff9110a0000(10000:f000) 7ff911080000(10000:f000) 7ff911070000(10000:f000) 7ff911060000(10000:f000) 7ff911050000(10000:f000) 7ff911040000(10000:f000) 7ff911020000(10000:f000) 7ff911010000(10000:f000) 7ff911000000(10000:f000) 7ff910ff0000(10000:f000) 7ff910fd0000(10000:f000) 7ff910fb0000(10000:f000) 7ff910fa0000(10000:f000) 7ff910f90000(10000:f000) 7ff910f80000(10000:f000) 7ff910f60000(10000:f000) 7ff910f50000(10000:f000) 7ff910f40000(10000:f000) 7ff910f30000(10000:f000) 7ff910f20000(10000:f000) 7ff910f00000(10000:f000) 7ff910ef0000(10000:f000) 7ff910ed0000(10000:f000) 7ff910ec0000(10000:f000) 7ff910eb0000(10000:f000) 7ff910e90000(10000:f000) 7ff910e80000(10000:f000) 7ff910e70000(10000:f000) 7ff910e60000(10000:f000) 7ff910e50000(10000:f000) 7ff910e30000(10000:f000) 7ff910e20000(10000:f000) 7ff910e10000(10000:f000) 7ff910df0000(10000:f000) 7ff910dd0000(10000:f000) 7ff910dc0000(10000:f000) 7ff910db0000(10000:f000) 7ff910da0000(10000:f000) 7ff910d90000(10000:f000) 7ff910d70000(10000:f000) 7ff910d60000(10000:f000) 7ff910d50000(10000:f000) 7ff910d40000(10000:f000) 7ff910d30000(10000:f000) 7ff910d00000(10000:f000) 7ff910cf0000(10000:f000) 7ff910ce0000(10000:f000) 7ff910cd0000(10000:f000) 7ff910cc0000(10000:f000) 7ff910ca0000(10000:f000) 7ff910c90000(10000:f000) 7ff910c80000(10000:f000) 7ff910c70000(10000:f000) 7ff910c50000(10000:f000) 7ff910c40000(10000:f000) 7ff910c30000(10000:f000) 7ff910c10000(10000:f000) 7ff910b80000(10000:10000) 7ff910b60000(10000:f000) 7ff910b50000(10000:f000) 7ff910b40000(10000:f000) 7ff910b30000(10000:f000) 7ff910b20000(10000:f000) 7ff910b00000(10000:f000) 7ff910af0000(10000:f000) 7ff910ae0000(10000:f000) 7ff910ad0000(10000:f000) 7ff910aa0000(10000:f000) 7ff910a90000(10000:f000) 7ff910a80000(10000:f000) 7ff910a70000(10000:f000) 7ff910a60000(10000:f000) 7ff910a40000(10000:f000) 7ff910a30000(10000:f000) 7ff910a20000(10000:f000) 7ff910a10000(10000:f000) 7ff910a00000(10000:f000) 7ff9109e0000(10000:f000) 7ff9109c0000(10000:f000) 7ff9109b0000(10000:f000) 7ff9109a0000(10000:f000) 7ff910990000(10000:f000) 7ff910970000(10000:f000) 7ff910HighFrequencyHeap:    7ff914230000(10000:c000) 7ff9141d0000(10000:10000) 7ff914180000(10000:10000) 7ff914110000(10000:10000) 7ff9140b0000(10000:10000) 7ff914050000(10000:10000) 7ff913ff0000(10000:10000) 7ff913f90000(10000:10000) 7ff913f20000(10000:10000) 7ff913ed0000(10000:10000) 7ff913e60000(10000:10000) 7ff913e00000(10000:10000) 7ff913d90000(10000:10000) 7ff913d40000(10000:10000) 7ff913ce0000(10000:10000) 7ff913c70000(10000:10000) 7ff913c10000(10000:10000) 7ff913bb0000(10000:10000) 7ff913b50000(10000:10000) 7ff913ae0000(10000:10000) 7ff913a90000(10000:10000) 7ff913a30000(10000:10000) 7ff9139c0000(10000:10000) 7ff913960000(10000:10000) 7ff913900000(10000:10000) 7ff9138a0000(10000:10000) 7ff913840000(10000:10000) 7ff9137d0000(10000:10000) 7ff913780000(10000:10000) 7ff913710000(10000:10000) 7ff9136b0000(10000:10000) 7ff913640000(10000:10000) 7ff9135f0000(10000:10000) 7ff913590000(10000:10000) 7ff913520000(10000:10000) 7ff9134d0000(10000:10000) 7ff913460000(10000:10000) 7ff913400000(10000:10000) 7ff9133a0000(10000:10000) 7ff913340000(10000:10000) 7ff9132e0000(10000:10000) 7ff913270000(10000:10000) 7ff913210000(10000:10000) 7ff9131b0000(10000:10000) 7ff913150000(10000:10000) 7ff9130f0000(10000:10000) 7ff913090000(10000:10000) 7ff912e20000(10000:10000) 7ff912dc0000(10000:10000) 7ff912d60000(10000:10000) 7ff912d00000(10000:10000) 7ff912c90000(10000:10000) 7ff912c40000(10000:10000) 7ff912bd0000(10000:10000) 7ff912b70000(10000:10000) 7ff912b00000(10000:10000) 7ff912ab0000(10000:10000) 7ff912a50000(10000:10000) 7ff9129e0000(10000:10000) 7ff912990000(10000:10000) 7ff912920000(10000:10000) 7ff9128c0000(10000:10000) 7ff912860000(10000:10000) 7ff912800000(10000:10000) 7ff9127a0000(10000:10000) 7ff912730000(10000:10000) 7ff9126d0000(10000:10000) 7ff912670000(10000:10000) 7ff912590000(10000:10000) 7ff912530000(10000:10000) 7ff9124c0000(10000:10000) 7ff912470000(10000:10000) 7ff912400000(10000:10000) 7ff9123a0000(10000:10000) 7ff912350000(10000:10000) 7ff9122e0000(10000:10000) 7ff912280000(10000:10000) 7ff912210000(10000:10000) 7ff9121c0000(10000:10000) 7ff912150000(10000:10000) 7ff9120f0000(10000:10000) 7ff912090000(10000:10000) 7ff912030000(10000:10000) 7ff911fd0000(10000:10000) 7ff911f60000(10000:10000) 7ff911f00000(10000:10000) 7ff911eb0000(10000:10000) 7ff911e40000(10000:10000) 7ff911de0000(10000:10000) 7ff911d00000(10000:10000) 7ff911ca0000(10000:10000) 7ff911c30000(10000:10000) 7ff911bd0000(10000:10000) 7ff911b80000(10000:10000) 7ff911b10000(10000:10000) 7ff911ab0000(10000:10000) 7ff911a40000(10000:10000) 7ff9119f0000(10000:10000) 7ff911980000(10000:10000) 7ff911920000(10000:10000) 7ff9118c0000(10000:10000) 7ff911860000(10000:10000) 7ff911800000(10000:10000) 7ff911790000(10000:10000) 7ff911740000(10000:10000) 7ff9116e0000(10000:10000) 7ff911670000(10000:10000) 7ff911610000(10000:10000) 7ff9115b0000(10000:10000) 7ff911550000(10000:10000) 7ff9114e0000(10000:10000) 7ff911400000(10000:10000) 7ff9113b0000(10000:10000) 7ff911340000(10000:10000) 7ff9112e0000(10000:10000) 7ff911270000(10000:10000) 7ff911220000(10000:10000) 7ff9111c0000(10000:10000) 7ff911150000(10000:10000) 7ff911100000(10000:10000) 7ff911090000(10000:10000) 7ff911030000(10000:10000) 7ff910fc0000(10000:10000) 7ff910f70000(10000:10000) 7ff910f10000(10000:10000) 7ff910ea0000(10000:10000) 7ff910e40000(10000:10000) 7ff910de0000(10000:10000) 7ff910d80000(10000:10000) 7ff910d20000(10000:10000) 7ff910cb0000(10000:10000) 7ff910c60000(10000:10000) 7ff910b70000(10000:10000) 7ff910b10000(10000:10000) 7ff910ab0000(10000:10000) 7ff910a50000(10000:10000) 7ff9109f0000(10000:10000) 7ff910980000(10000:10000) 7ff910930000(10000:10000) 7ff9108c0000(10000:10000) 7ff910860000(10000:10000) 7ff910800000(10000:10000) 7ff9107a0000(10000:10000) 7ff910740000(10000:10000) 7ff9106d0000(10000:10000) 7ff910670000(10000:10000) 7ff910610000(10000:10000) 7ff9105b0000(10000:10000) 7ff910550000(10000:10000) 7ff9104f0000(10000:10000) 7ff910490000(10000:10000) 7ff910420000(10000:10000) 7ff9103c0000(10000:10000) 7ff910370000(10000:10000) 7ff910280000(10000:10000) 7ff910220000(10000:10000) 7ff9101b0000(10000:10000) 7ff910160000(10000:10000) 7ff9100f0000(10000:10000) 7ff910090000(10000:10000) 7ff910030000(10000:10000) 7ff90ffd0000(10000:10000) 7ff90ff70000(10000:10000) 7ff90ff00000(10000:10000) 7ff90feb0000(10000:10000) 7ff90fe50000(10000:10000) 7ff90fde0000(10000:10000) 7ff90fd80000(10000:10000) 7ff90fd20000(10000:10000) 7ff90fcc0000(10000:10000) 7ff90fc50000(10000:10000) 7ff90fbf0000(10000:10000) 7ff90fba0000(10000:10000) 7ff90fb50000(10000:10000) 7ff90fb30000(10000:10000) 7ff90fb00000(10000:10000) 7ff90fae0000(10000:10000) 7ff90fab0000(10000:10000) 7ff90fa90000(10000:10000) 7ff90fa70000(10000:10000) 7ff90fa40000(10000:10000) 7ff90fa20000(10000:10000) 7ff90fa00000(10000:10000) 7ff90f9e0000(10000:10000) 7ff90f9b0000(10000:10000) 7ff90f980000(10000:10000) 7ff90f960000(10000:10000) 7ff90f8f0000(10000:f000) 7ff90f8c0000(10000:10000) 7ff90f820000(10000:10000) 7ff90f7f0000(10000:10000) 7ff90f7d0000(10000:10000) 7ff90f7b0000(10000:10000) 7ff90f770000(10000:10000) 7ff90f6e4000(9000:6000) Size: 0xc21000 (12718080) bytes total, 0x4000 (16384) bytes wasted.
 7 StubHeap:             7ff90f6ed000(3000:1000) Size: 0x1000 (4096) bytes total.
 8 FixupPrecodeHeap:     7ff914250000(10000:8000) 7ff914160000(10000:10000) 7ff914070000(10000:10000) 7ff913f80000(10000:10000) 7ff913ea0000(10000:10000) 7ff913db0000(10000:10000) 7ff913cc0000(10000:10000) 7ff913be0000(10000:10000) 7ff913af0000(10000:10000) 7ff913a00000(10000:10000) 7ff913920000(10000:10000) 7ff913820000(10000:10000) 7ff913740000(10000:10000) 7ff913660000(10000:10000) 7ff913560000(10000:10000) 7ff913480000(10000:10000) 7ff913390000(10000:10000) 7ff9132a0000(10000:10000) 7ff9131c0000(10000:10000) 7ff9130d0000(10000:10000) 7ff912de0000(10000:10000) 7ff912cf0000(10000:10000) 7ff912c00000(10000:10000) 7ff912b20000(10000:10000) 7ff912a30000(10000:10000) 7ff912940000(10000:10000) 7ff912850000(10000:10000) 7ff912770000(10000:10000) 7ff912680000(10000:10000) 7ff912510000(10000:10000) 7ff912430000(10000:10000) 7ff912330000(10000:10000) 7ff912250000(10000:10000) 7ff912170000(10000:10000) 7ff912070000(10000:10000) 7ff911f90000(10000:10000) 7ff911ea0000(10000:10000) 7ff911db0000(10000:10000) 7ff911c50000(10000:10000) 7ff911b60000(10000:10000) 7ff911a70000(10000:10000) 7ff911990000(10000:10000) 7ff9118a0000(10000:10000) 7ff9117b0000(10000:10000) 7ff9116c0000(10000:10000) 7ff9115e0000(10000:10000) 7ff9114f0000(10000:10000) 7ff911380000(10000:10000) 7ff9112a0000(10000:10000) 7ff9111a0000(10000:10000) 7ff9110c0000(10000:10000) 7ff910fe0000(10000:10000) 7ff910ee0000(10000:10000) 7ff910e00000(10000:10000) 7ff910d10000(10000:10000) 7ff910c20000(10000:10000) 7ff910ac0000(10000:10000) 7ff9109d0000(10000:10000) 7ff9108e0000(10000:10000) 7ff9107f0000(10000:10000) 7ff910710000(10000:10000) 7ff910620000(10000:10000) 7ff910530000(10000:10000) 7ff910450000(10000:10000) 7ff910350000(10000:10000) 7ff9101f0000(10000:10000) 7ff910110000(10000:10000) 7ff910010000(10000:10000) 7ff90ff30000(10000:10000) 7ff90fe40000(10000:10000) 7ff90fd50000(10000:10000) 7ff90fc70000(10000:10000) 7ff90fb80000(10000:10000) 7ff90fb40000(10000:10000) 7ff90fb20000(10000:10000) 7ff90fb10000(10000:10000) 7ff90faf0000(10000:10000) 7ff90fac0000(10000:10000) 7ff90fa80000(10000:10000) 7ff90fa50000(10000:10000) 7ff90fa30000(10000:10000) 7ff90f9f0000(10000:10000) 7ff90f9d0000(10000:10000) 7ff90f9a0000(10000:10000) 7ff90f990000(10000:10000) 7ff90f910000(10000:10000) 7ff90f8e0000(10000:10000) 7ff90f830000(10000:10000) 7ff90f810000(10000:10000) 7ff90f7e0000(10000:10000) 7ff90f7c0000(10000:10000) 7ff90f780000(10000:10000) Size: 0x5b8000 (5996544) bytes total.
 9 NewStubPrecodeHeap:   7ff90fb60000(10000:8000) 7ff90f790000(10000:10000) Size: 0x18000 (98304) bytes total.
10 IndirectionCellHeap:  7ff90f6f0000(6000:1000) Size: 0x1000 (4096) bytes total.
11 CacheEntryHeap:       7ff90f6f6000(a000:1000) Size: 0x1000 (4096) bytes total.
12 Total size:           Size: 0x4340000 (70516736) bytes total, 0x33e000 (3399680) bytes wasted.
13 ----------------------------------------
14 Domain 1:             01be298ed190
15 LoaderAllocator:      01be298ed190
16 No unique loader heaps found.
17 ----------------------------------------
18 JIT Manager:          01be2992cb30
19 LoaderCodeHeap:       7ff90f840000(80000:77000) Size: 0x77000 (487424) bytes total.
20 LoaderCodeHeap:       7ff9102d0000(80000:7f000) Size: 0x7f000 (520192) bytes total.
21 LoaderCodeHeap:       7ff910b90000(80000:7f000) Size: 0x7f000 (520192) bytes total.
22 LoaderCodeHeap:       7ff911440000(80000:7f000) Size: 0x7f000 (520192) bytes total.
23 LoaderCodeHeap:       7ff911d10000(80000:7f000) Size: 0x7f000 (520192) bytes total.
24 LoaderCodeHeap:       7ff9125c0000(80000:7f000) Size: 0x7f000 (520192) bytes total.
25 LoaderCodeHeap:       7ff912e70000(200000:116000) Size: 0x116000 (1138688) bytes total.
26 Total size:           Size: 0x408000 (4227072) bytes total.
27 ----------------------------------------
28 
29 ========================================
30 Number of GC Heaps: 1
31 ----------------------------------------
32 Small object heap
33          segment            begin        allocated        committed allocated size     committed size    
34 generation 0:
35     01fe3f44f320     01be2e000028     01be2e120128     01be2e361000 0x120100 (1179904) 0x361000 (3543040)
36 generation 1:
37     01fe3f44f270     01be2dc00028     01be2dc0da38     01be2dc51000 0xda10 (55824)     0x51000 (331776)  
38 generation 2:
39     01fe3f44f1c0     01be2d800028     01be2d89ac20     01be2d8a1000 0x9abf8 (633848)   0xa1000 (659456)  
40 NonGC heap
41          segment            begin        allocated        committed allocated size     committed size    
42     01be2992eee0     01febff50008     01fec0023d90     01fec0030000 0xd3d88 (867720)   0xe0000 (917504)  
43 Large object heap
44          segment            begin        allocated        committed allocated size     committed size    
45     01fe3f44f3d0     01be2e400028     01be2e400028     01be2e401000                    0x1000 (4096)     
46 Pinned object heap
47          segment            begin        allocated        committed allocated size     committed size    
48     01fe3f44ec40     01be2b800028     01be2b823e78     01be2b831000 0x23e50 (147024)   0x31000 (200704)  
49 ------------------------------
50 GC Allocated Heap Size:    Size: 0x2c02e0 (2884320) bytes.
51 GC Committed Heap Size:    Size: 0x565000 (5656576) bytes.
52 
53 Total bytes consumed by CLR: 0x4cad000 (80400384)

                  當我看到這個結果的時候,我感覺沒有什麼異常,除了這應用程式域【Domain 1】,它提示說:No unique loader heaps found. 沒有找到唯一的載入堆,說明就是有多個,這個應用程式的地址是:01be298ed190,我們針對這個地址檢視一下這個應用程式域的詳情,執行命令【!DumpDomain 01be298ed190】。

  1 0:008> !DumpDomain 01be298ed190
  2 --------------------------------------
  3 Domain 1:           000001be298ed190
  4 LowFrequencyHeap:   00007FF96F6D4598
  5 HighFrequencyHeap:  00007FF96F6D4628
  6 StubHeap:           00007FF96F6D46B8
  7 Stage:              OPEN
  8 Name:               clrhost
  9 Assembly:           000001be29925af0 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.4\System.Private.CoreLib.dll]
 10 ClassLoader:        000001BE298F0130
 11   Module
 12   00007ff90f6e4000    C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.4\System.Private.CoreLib.dll
 13 
 14 Assembly:           000001be29925550 [E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_5_10\bin\Debug\net8.0\ExampleCore_5_10.dll]
 15 ClassLoader:        000001BE299B7E30
 16   Module
 17   00007ff90f8ce0a0    E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_5_10\bin\Debug\net8.0\ExampleCore_5_10.dll
 18 
 19 Assembly:           000001be29925610 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.4\System.Runtime.dll]
 20 ClassLoader:        000001BE299B7B70
 21   Module
 22   00007ff90f8cfbc8    C:\Program Files\dotnet\shared\Microsoft.NETCore.App\8.0.4\System.Runtime.dll
 23 
 24 ......(太多了,省略了)
 25 
 26 Assembly:           000001fec03845c0 (Dynamic) []
 27 ClassLoader:        000001BE2B3E8360
 28   Module
 29   00007ff90fb5acf8    Dynamic Module
 30 
 31 Assembly:           000001fec0384560 (Dynamic) []
 32 ClassLoader:        000001BE2B3E9D80
 33   Module
 34   00007ff90fb5b7c0    Dynamic Module
 35 
 36 Assembly:           000001fec0385040 (Dynamic) []
 37 ClassLoader:        000001BE2B3E8D00
 38   Module
 39   00007ff90fb5c288    Dynamic Module
 40 
 41 Assembly:           000001fec0384bc0 (Dynamic) []
 42 ClassLoader:        000001BE2B3E7F40
 43   Module
 44   00007ff90fb5cd50    Dynamic Module
 45 
 46 ......(太多了,省略了)
 47 
 48 Assembly:           000001fec49d52d0 (Dynamic) []
 49 ClassLoader:        000001FEC470FD30
 50   Module
 51   00007ff910f76ea0    Dynamic Module
 52 
 53 Assembly:           000001fec49d5690 (Dynamic) []
 54 ClassLoader:        000001FEC47106D0
 55   Module
 56   00007ff910f77968    Dynamic Module
 57 
 58 Assembly:           000001fec49d4eb0 (Dynamic) []
 59 ClassLoader:        000001FEC47102B0
 60   Module
 61   00007ff910f78430    Dynamic Module
 62 
 63 Assembly:           000001fec49d4970 (Dynamic) []
 64 ClassLoader:        000001FEC4710830
 65   Module
 66   00007ff910f78ef8    Dynamic Module
 67 
 68 Assembly:           000001fec49d6ef0 (Dynamic) []
 69 ClassLoader:        000001FEC4710620
 70   Module
 71   00007ff910f799c0    Dynamic Module
 72 
 73 ......(太多了,省略了)
 74 
 75 Assembly:           000001fec78939e0 (Dynamic) []
 76 ClassLoader:        000001FEC710CF30
 77   Module
 78   00007ff9126d4380    Dynamic Module
 79 
 80 Assembly:           000001fec7f35620 (Dynamic) []
 81 ClassLoader:        000001FEC710CB10
 82   Module
 83   00007ff9126d4e48    Dynamic Module
 84 
 85 Assembly:           000001fec7f356e0 (Dynamic) []
 86 ClassLoader:        000001FEC710CE80
 87   Module
 88   00007ff9126d5910    Dynamic Module
 89 
 90 Assembly:           000001fec7f35380 (Dynamic) []
 91 ClassLoader:        000001FEC710D090
 92   Module
 93   00007ff9126d63d8    Dynamic Module
 94 
 95 Assembly:           000001fec7f342a0 (Dynamic) []
 96 ClassLoader:        000001FEC710D140
 97   Module
 98   00007ff9126d6ea0    Dynamic Module
 99 
100 Assembly:           000001fec7f350e0 (Dynamic) []
101 ClassLoader:        000001FEC710D1F0
102   Module
103   00007ff9126d7968    Dynamic Module
104 
105 Assembly:           000001fec7f34960 (Dynamic) []
106 ClassLoader:        000001FEC710D2A0
107   Module
108   00007ff9126d8430    Dynamic Module
109 
110 ......(太多了,省略了)
111 
112 Assembly:           000001fecb90c5c0 (Dynamic) []
113 ClassLoader:        000001FECA8B0EF0
114   Module
115   00007ff914237968    Dynamic Module
116 
117 Assembly:           000001fecb90b480 (Dynamic) []
118 ClassLoader:        000001FECA8B0FA0
119   Module
120   00007ff914238430    Dynamic Module
121 
122 Assembly:           000001fecb90b8a0 (Dynamic) []
123 ClassLoader:        000001FECA8B4C20
124   Module
125   00007ff914238ef8    Dynamic Module
126 
127 Assembly:           000001fecb90c860 (Dynamic) []
128 ClassLoader:        000001FECA8B4960
129   Module
130   00007ff9142399c0    Dynamic Module
131 
132 Assembly:           000001fecb90c980 (Dynamic) []
133 ClassLoader:        000001FECA8B4280
134   Module
135   00007ff91423a488    Dynamic Module
136 
137 Assembly:           000001fecb90c920 (Dynamic) []
138 ClassLoader:        000001FECA8B4070
139   Module
140   00007ff91423af50    Dynamic Module

                  我們看到了吧,紅色的部分,我省略了很多,如果全部輸出,太多了。從表面上看,也能猜到有問題。在我們應用程式域中載入了很多動態建立的程式集,就是這個東西導致的記憶體洩漏。
                  我們以最後一個為例,模組的型別也是【Dynamic Module】,地址是:00007ff91423af50,我們再使用【!DumpModule 00007ff91423af50】命令,檢視一下這個模組的資訊。

 1 0:008> !DumpModule 00007ff91423af50
 2 Name: Unknown Module
 3 Attributes:              Reflection IsDynamic IsInMemory 
 4 TransientFlags:          00200811 
 5 Assembly:                000001fecb90c920
 6 BaseAddress:             0000000000000000
 7 PEAssembly:              000001FECB9B0510
 8 ModuleId:                00007FF91423B350
 9 ModuleIndex:             0000000000001012
10 LoaderHeap:              00007FF96F6D4588
11 TypeDefToMethodTableMap: 00007FF91427E100
12 TypeRefToMethodTableMap: 00007FF91427E128
13 MethodDefToDescMap:      00007FF91427E1A0
14 FieldDefToDescMap:       00007FF91427E1F0
15 MemberRefToDescMap:      00007FF91427E150
16 FileReferencesMap:       0000000000000000
17 AssemblyReferencesMap:   00007FF91427E290

                  我們看到這個模組的特性【Attributes】的值是 Reflection IsDynamic IsInMemoryReflection 表示是反射的,IsDynamic 表示是動態的(不是我們手動建立的),IsInMemory 表示是在記憶體中建立生成的,這三個詞就是說,這個模組式是透過反射技術在記憶體中動態建立的。
                  有一點,我們可以知道,就是這些程式集是我們的程式動態建立的,那我們去託管執行緒呼叫棧看看不就清楚了。
                  首先,我們要執行【~0s】命令切換到託管執行緒上下文中,然後,執行【!clrstack】命令。

 1 0:000> !clrstack
 2 OS Thread Id: 0x22e4 (0)
 3         Child SP               IP Call Site
 4 000000731797DDE0 00007ff96f281487 [InlinedCallFrame: 000000731797dde0] 
 5 000000731797DDE0 00007ff96e7ea6ac [InlinedCallFrame: 000000731797dde0] 
 6 000000731797DDB0 00007ff96e7ea6ac System.RuntimeTypeHandle.GetInstantiationPublic() [/_/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs @ 527]
 7 000000731797DEB0 00007ff96e7f0e4d System.RuntimeType.GetGenericArguments() [/_/src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs @ 3501]
 8 000000731797DEE0 00007ff96e984e55 System.Reflection.Emit.RuntimeModuleBuilder.GetGenericMethodBaseDefinition(System.Reflection.MethodBase) [/_/src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/RuntimeModuleBuilder.cs @ 430]
 9 000000731797DF30 00007ff96e984afa System.Reflection.Emit.RuntimeModuleBuilder.GetMemberRefToken(System.Reflection.MethodBase, System.Type[]) [/_/src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/RuntimeModuleBuilder.cs @ 338]
10 000000731797DFB0 00007ff96e986f12 System.Reflection.Emit.RuntimeModuleBuilder.GetMethodTokenInternal(System.Reflection.MethodBase, System.Type[], Boolean) [/_/src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/RuntimeModuleBuilder.cs @ 1123]
11 000000731797E040 00007ff96e97fd59 System.Reflection.Emit.RuntimeILGenerator.EmitCall(System.Reflection.Emit.OpCode, System.Reflection.MethodInfo, System.Type[]) [/_/src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/RuntimeILGenerator.cs @ 607]
12 000000731797E0C0 00007ff96e97f724 System.Reflection.Emit.RuntimeILGenerator.Emit(System.Reflection.Emit.OpCode, System.Reflection.MethodInfo) [/_/src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/RuntimeILGenerator.cs @ 494]
13 000000731797E110 00007ff96dfe82c0 System.Xml.Serialization.XmlSerializationReaderILGen.ILGenParamsReadSource(System.String, Boolean) [/_/src/libraries/System.Private.Xml/src/System/Xml/Serialization/XmlSerializationReaderILGen.cs @ 3622]
14 000000731797E170 00007ff96dfe3faf System.Xml.Serialization.XmlSerializationReaderILGen.WriteMemberElementsIf(Member[], Member, System.String) [/_/src/libraries/System.Private.Xml/src/System/Xml/Serialization/XmlSerializationReaderILGen.cs @ 2573]
15 000000731797E2C0 00007ff96dfe30ba System.Xml.Serialization.XmlSerializationReaderILGen.WriteMemberElements(Member[], System.String, System.String, Member, Member) [/_/src/libraries/System.Private.Xml/src/System/Xml/Serialization/XmlSerializationReaderILGen.cs @ 2274]
16 000000731797E340 00007ff96dfe0244 System.Xml.Serialization.XmlSerializationReaderILGen.WriteLiteralStructMethod(System.Xml.Serialization.StructMapping) [/_/src/libraries/System.Private.Xml/src/System/Xml/Serialization/XmlSerializationReaderILGen.cs @ 1680]
17 000000731797E620 00007ff96dfc5871 System.Xml.Serialization.XmlSerializationILGen.GenerateReferencedMethods() [/_/src/libraries/System.Private.Xml/src/System/Xml/Serialization/XmlSerializationILGen.cs @ 114]
18 000000731797E650 00007ff96dfd9dd5 System.Xml.Serialization.XmlSerializationReaderILGen.GenerateEnd() [/_/src/libraries/System.Private.Xml/src/System/Xml/Serialization/XmlSerializationReaderILGen.cs @ 225]
19 000000731797E6F0 00007ff96df8bc69 System.Xml.Serialization.TempAssembly.GenerateRefEmitAssembly(System.Xml.Serialization.XmlMapping[], System.Type[]) [/_/src/libraries/System.Private.Xml/src/System/Xml/Serialization/Compilation.cs @ 508]
20 000000731797E860 00007ff96df8ac4c System.Xml.Serialization.TempAssembly..ctor(System.Xml.Serialization.XmlMapping[], System.Type[], System.String, System.String) [/_/src/libraries/System.Private.Xml/src/System/Xml/Serialization/Compilation.cs @ 66]
21 000000731797E8D0 00007ff96e004dcd System.Xml.Serialization.XmlSerializer.GenerateTempAssembly(System.Xml.Serialization.XmlMapping, System.Type, System.String, System.String) [/_/src/libraries/System.Private.Xml/src/System/Xml/Serialization/XmlSerializer.cs @ 316]
22 000000731797E930 00007ff96e004673 System.Xml.Serialization.XmlSerializer..ctor(System.Type, System.Xml.Serialization.XmlRootAttribute) [/_/src/libraries/System.Private.Xml/src/System/Xml/Serialization/XmlSerializer.cs @ 176]
23 000000731797E980 00007ff90f841b74 ExampleCore_5_10.Program.Run() [E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_5_10\Program.cs @ 68]
24 000000731797EA40 00007ff90f841988 ExampleCore_5_10.Program.Main(System.String[]) [E:\Visual Studio 2022\Source\Projects\AdvancedDebug.NetFramework.Test\ExampleCore_5_10\Program.cs @ 48]

                  我們看到了 System.Xml.Serialization.XmlSerializer..ctor 型別的例項化,在該型別內部又呼叫了 System.Xml.Serialization.XmlSerializer.GenerateTempAssembly 建立臨時程式集,看來問題我們找到了,那就容易了。

                  我們再說一種情況,如果程式直接執行到崩潰,並報告一個 OutOfMemoryException 的異常怎麼辦?當然,這樣又分兩種情況,如果程式是自己的,可以透過偵錯程式附近程序進行除錯,如果程式是別人的,不能直接除錯程式,就只能透過抓 Dump 來實現除錯了。抓 Dump 有兩種方式,一種是使用【工作管理員】,一種是使用【ProcessExplorer】工具。
                  這個除錯就不演示了,大概過程就是,我們【g】繼續,直到我們的程式崩潰,丟擲記憶體溢位的異常為止,我們可以使用【kb】命令找到異常地址,再使用【!PrintException】列印異常資訊,也可以找到具體的問題。


四、總結
    這篇文章的終於寫完了,這篇文章的內容相對來說,不是很多。寫完一篇,就說明進步了一點點。Net 高階除錯這條路,也剛剛起步,還有很多要學的地方。皇天不負有心人,努力,不辜負自己,我相信付出就有回報,再者說,學習的過程,有時候,雖然很痛苦,但是,學有所成,學有所懂,這個開心的感覺還是不可言喻的。不忘初心,繼續努力。做自己喜歡做的,開心就好。

相關文章