1 namespace Example_12_1_1 2 { 3 internal class Program 4 { 5 static void Main(string[] args) 6 { 7 Console.WriteLine("請輸入任一字串。。。"); 8 var str=Console.ReadLine(); 9 10 Console.WriteLine("請觀察 str 是否在 0 代!"); 11 Debugger.Break(); 12 13 GC.Collect(); 14 Console.WriteLine("請觀察 str 是否在 1 代!"); 15 Debugger.Break(); 16 17 GC.Collect(); 18 Console.WriteLine("請觀察 str 是否在 2 代!"); 19 Debugger.Break(); 20 } 21 } 22 }
1 namespace Example_12_1_2 2 { 3 internal class Program 4 { 5 public static Person3 person3 = new Person3(); 6 static void Main(string[] args) 7 { 8 var person1 = new Person1(); 9 10 FinalizeTest(); 11 12 Console.WriteLine("分配完畢!"); 13 14 Console.ReadLine(); 15 } 16 17 private static void FinalizeTest() 18 { 19 var person2 = new Person2(); 20 } 21 } 22 }
Person1 類原始碼:
1 internal class Person1 2 { 3 }
Person2 類原始碼:
1 internal class Person2 2 { 3 ~Person2() 4 { 5 Console.WriteLine("我是解構函式"); 6 } 7 }
Person3 類原始碼:
1 internal class Person3 2 { 3 }
1 namespace Example_12_1_3 2 { 3 internal class Program 4 { 5 static void Main(string[] args) 6 { 7 TestFinalize(); 8 9 Console.WriteLine("開始觸發GC了!"); 10 GC.Collect(); 11 12 Console.ReadLine(); 13 } 14 15 private static void TestFinalize() 16 { 17 var person = new Person(); 18 } 19 } 20 }
Person 類原始碼:
1 internal class Person 2 { 3 ~Person() 4 { 5 Console.WriteLine("我是解構函式"); 6 7 Console.ReadLine(); 8 } 9 }
1 namespace Example_12_1_4 2 { 3 internal class Program 4 { 5 static void Main(string[] args) 6 { 7 Test(); 8 9 Console.WriteLine("1、物件已經分配,請檢視託管堆!"); 10 Debugger.Break(); 11 GC.Collect(); 12 13 Console.WriteLine("2、GC 已經觸發,請檢視託管堆中的 byte2"); 14 Debugger.Break(); 15 16 Console.WriteLine("3、已分配 byte4,檢視是否 Free 塊中"); 17 var byte4 = new byte[280000]; 18 Debugger.Break(); 19 } 20 21 public static byte[] byte1; 22 public static byte[] byte3; 23 24 private static void Test() 25 { 26 byte1 = new byte[185000]; 27 var byte2 = new byte[285000]; 28 byte3 = new byte[385000]; 29 } 30 } 31 }
1 0:000> !clrstack -l 2 OS Thread Id: 0x323c (0) 3 Child SP IP Call Site 4 012fec70 766ef262 [HelperMethodFrame: 012fec70] System.Diagnostics.Debugger.BreakInternal() 5 012fecec 6e45f195 System.Diagnostics.Debugger.Break() [f:\dd\ndp\clr\src\BCL\system\diagnostics\debugger.cs @ 91] 6 7 012fed14 031a087d Example_12_1_1.Program.Main(System.String[]) [E:\Visual Studio 2022...\Example_12_1_1\Program.cs @ 16] 8 LOCALS: 9 <CLR reg> = 0x033c4e80 我們的區域性變數。 10 11 012fee88 7033f036 [GCFrame: 012fee88]
我們看到,紅色標記的就是區域性變數。我們看看它的內容,使用【!dumpobj /d 0x033c4e80 】。
1 0:000> !dumpobj /d 0x033c4e80 2 Name: System.String 3 MethodTable: 6d8e24e4 4 EEClass: 6d9e7690 5 Size: 54(0x36) bytes 6 File: C:\Windows\Microsoft.Net\assembly\GAC_32\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll 7 String: aaaaaaaaaaaaaaaaaaaa 我們輸入的值。 8 Fields: 9 MT Field Offset Type VT Attr Value Name 10 6d8e42a8 4000283 4 System.Int32 1 instance 20 m_stringLength 11 6d8e2c9c 4000284 8 System.Char 1 instance 61 m_firstChar 12 6d8e24e4 4000288 70 System.String 0 shared static Empty 13 >> Domain:Value 014e2318:NotInit <<
我們的程式輸出:請觀察 aaaaaaaaaaaaaaaaaaaa 是否在 0 代!我們使用【!gcwhere 0x033c4e80】命令就可以看到這個字串在堆上的情況。
1 0:000> !gcwhere 0x033c4e80 2 Address Gen Heap segment begin allocated size 3 033c4e80 0 0 033c0000 033c1000 033c5ff4 0x38(56)
當前字串還沒有執行垃圾回收,所以在 0 代。我們繼續【g】,程式輸出:請觀察 aaaaaaaaaaaaaaaaaaaa 是否在 1 代!我們繼續使用【!gcwhere 0x033c4e80】命令檢視具體的情況。
1 0:000> !gcwhere 0x033c4e80 2 Address Gen Heap segment begin allocated size 3 033c4e80 1 0 033c0000 033c1000 033c7150 0x38(56)
我們的字串已經在 1 代了。我們繼續【g】,程式輸出:請觀察 aaaaaaaaaaaaaaaaaaaa 是否在 2 代!!我們繼續使用【!gcwhere 0x033c4e80】命令檢視具體的情況。
1 0:000> !gcwhere 0x033c4e80 2 Address Gen Heap segment begin allocated size 3 033c4e80 2 0 033c0000 033c1000 033c715c 0x38(56)
1 0:000> !clrstack -a 2 OS Thread Id: 0x31a4 (0) 3 Child SP IP Call Site 4 00b8f2dc 77e710fc [InlinedCallFrame: 00b8f2dc] 5 ...... 6 00b8f3d8 02a50929 Example_12_1_2.Program.Main(System.String[]) [E:\Visual Studio 2022\\Example_12_1_2\Program.cs @ 16] 7 PARAMETERS: 8 args (0x00b8f3e4) = 0x02c924c8 9 LOCALS: 10 0x00b8f3e0 = 0x02c924f8 11 12 00b8f560 7158f036 [GCFrame: 00b8f560]
0x02c924f8 就是 Person1物件的地址,我們可以使用【!DumpObj /d 02c924f8】命令檢視 Person1的詳情。
1 0:000> !DumpObj /d 02c924f8 2 Name: Example_12_1_2.Person1 3 MethodTable: 010d4e80 4 EEClass: 010d13e4 5 Size: 12(0xc) bytes 6 File: E:\Visual Studio 2022\Example_12_1_2\bin\Debug\Example_12_1_2.exe 7 Fields: 8 None
的確是 Person1 物件,我們繼續使用【!gcroot 02c924f8】命令檢視在哪裡被參照了。
1 0:000> !gcroot 02c924f8 2 Thread 31a4: 3 00b8f3d8 02a50929 Example_12_1_2.Program.Main(System.String[]) [E:\Visual Studio 2022\Example_12_1_2\Program.cs @ 16] 4 ebp+8: 00b8f3e0(這個是棧地址,和 !clrstack -a 結果中的 LOCALS: 0x00b8f3e0(棧地址) = 0x02c924f8(物件地址)) 5 -> 02c924f8 Example_12_1_2.Person1 6 7 Found 1 unique roots (run '!GCRoot -all' to see all roots).
1 0:000> !dumpheap -type Person2 2 Address MT Size 3 029c2508 00884e8c 12 4 5 Statistics: 6 MT Count TotalSize Class Name 7 00884e8c 1 12 Example_12_1_2.Person2
我們找到了 Person2 物件的地址:029c2508,我們可以檢視是否是 Person2。
1 0:000> !DumpObj /d 029c2508 2 Name: Example_12_1_2.Person2 3 MethodTable: 00884e8c 4 EEClass: 008813a8 5 Size: 12(0xc) bytes 6 File: E:\Visual Studio 2022\Example_12_1_2\bin\Release\Example_12_1_2.exe 7 Fields: 8 None
我們有了 Person2 物件地址,我們可以執行【!gcroot】命令,看看還有誰參照。
1 0:000> !gcroot 029c2508 2 Finalizer Queue(終端子佇列): 3 029c2508 4 -> 029c2508 Example_12_1_2.Person2 5 6 Warning: These roots are from finalizable objects that are not yet ready for finalization. 7 This is to handle the case where objects re-register themselves for finalization. 8 These roots may be false positives. 9 Found 1 unique roots (run '!GCRoot -all' to see all roots).
1 0:000> !dumpheap -type Person3 2 Address MT Size 3 02c924d4 010d4e24 12 4 5 Statistics: 6 MT Count TotalSize Class Name 7 010d4e24 1 12 Example_12_1_2.Person3 8 Total 1 objects
紅色標記的就是 Person3 物件的地址,我們直接使用【!gcroot 02c924d4】命令看一看。
1 0:000> !gcroot 02c924d4 2 HandleTable: 3 010b13ec (pinned handle)(pinned) 4 -> 03c93568 System.Object[](控制程式碼表地址) 5 -> 02c924d4 Example_12_1_2.Person3 6 7 Found 1 unique roots (run '!GCRoot -all' to see all roots).
如果想檢視控制程式碼表的詳情,可以執行如下命令。
1 0:000> !da -details 03c93568 2 Name: System.Object[] 3 MethodTable: 700b2788 4 EEClass: 701b7820 5 Size: 8172(0x1fec) bytes 6 Array: Rank 1, Number of elements 2040, Type CLASS 7 Element Methodtable: 700b2734 8 ......
1 0:000> !fq 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 8 finalizable objects (01585990->015859b0)【0代有8個可以被回收的物件。】 8 generation 1 has 0 finalizable objects (01585990->01585990)【1代有0個可以被回收的物件。】 9 generation 2 has 0 finalizable objects (01585990->01585990)【2代有0個可以被回收的物件。】 10 Ready for finalization 0 objects (015859b0->015859b0),【015859b0->015859b0】這個區間會被終端子執行緒讀取的,就可以釋放這個區間的資源。 11 Statistics for all finalizable objects (including all objects ready for finalization): 12 MT Count TotalSize Class Name 13 01954780 1 12 Example_12_1_2.Person2 14 0338c890 1 20 Microsoft.Win32.SafeHandles.SafeFileMappingHandle 15 0338c808 1 20 Microsoft.Win32.SafeHandles.SafeViewOfFileHandle 16 0335b7ac 1 20 Microsoft.Win32.SafeHandles.SafePEFileHandle 17 03389370 2 40 Microsoft.Win32.SafeHandles.SafeFileHandle 18 0335c274 1 44 System.Threading.ReaderWriterLock 19 0335133c 1 52 System.Threading.Thread 20 Total 8 objects
1 0:000> !t 2 ThreadCount: 2 3 UnstartedThread: 0 4 BackgroundThread: 1 5 PendingThread: 0 6 DeadThread: 0 7 Hosted Runtime: no 8 Lock 9 ID OSID ThreadOBJ State GC Mode GC Alloc Context Domain Count Apt Exception 10 0 1 620 0157a640 2a020 Preemptive 033A4F40:00000000 01542228 1 MTA 11 5 2 108c 015491a0 2b220 Preemptive 00000000:00000000 01542228 0 MTA (Finalizer) (終端子執行緒) 12 13 0:000> !threads 14 ThreadCount: 2 15 UnstartedThread: 0 16 BackgroundThread: 1 17 PendingThread: 0 18 DeadThread: 0 19 Hosted Runtime: no 20 Lock 21 ID OSID ThreadOBJ State GC Mode GC Alloc Context Domain Count Apt Exception 22 0 1 620 0157a640 2a020 Preemptive 033A4F40:00000000 01542228 1 MTA 23 5 2 108c 015491a0 2b220 Preemptive 00000000:00000000 01542228 0 MTA (Finalizer) (終端子執行緒)
1 0:000> !fq 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 8 finalizable objects (01585990->015859b0) 8 generation 1 has 0 finalizable objects (01585990->01585990) 9 generation 2 has 0 finalizable objects (01585990->01585990) 10 Ready for finalization 0 objects (015859b0->015859b0)(如果有物件在這個區間,終端子執行緒才會執行) 11 Statistics for all finalizable objects (including all objects ready for finalization): 12 MT Count TotalSize Class Name 13 01954780 1 12 Example_12_1_2.Person2 14 0338c890 1 20 Microsoft.Win32.SafeHandles.SafeFileMappingHandle 15 0338c808 1 20 Microsoft.Win32.SafeHandles.SafeViewOfFileHandle 16 0335b7ac 1 20 Microsoft.Win32.SafeHandles.SafePEFileHandle 17 03389370 2 40 Microsoft.Win32.SafeHandles.SafeFileHandle 18 0335c274 1 44 System.Threading.ReaderWriterLock 19 0335133c 1 52 System.Threading.Thread 20 Total 8 objects
如果沒有物件在這個區間,終端子執行緒會處於等待狀態。
1 0:000> !t 2 ThreadCount: 2 3 UnstartedThread: 0 4 BackgroundThread: 1 5 PendingThread: 0 6 DeadThread: 0 7 Hosted Runtime: no 8 Lock 9 ID OSID ThreadOBJ State GC Mode GC Alloc Context Domain Count Apt Exception 10 0 1 190c 010e3980 202a020 Preemptive 02D36E98:00000000 010dd7c0 0 MTA 11 5 2 108c 01120738 2b220 Preemptive 02D34B00:00000000 010dd7c0 1 MTA (Finalizer)
我們切換到終端子執行緒,執行命令【~~[108c]s】
1 0:000> ~~[108c]s 2 eax=00000000 ebx=000000a0 ecx=00000000 edx=00000000 esi=04ecfa68 edi=00000000 3 eip=777c10fc esp=04ecf950 ebp=04ecf9b0 iopl=0 nv up ei pl nz ac pe nc 4 cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000216 5 ntdll!NtReadFile+0xc: 6 777c10fc c22400 ret 24h
我們檢視一下當前執行緒的呼叫棧。
1 0:005> !clrstack 2 OS Thread Id: 0x108c (5) 3 Child SP IP Call Site 4 04ecf9d0 777c10fc [InlinedCallFrame: 04ecf9d0] 5 04ecf9cc 052a12db DomainNeutralILStubClass.IL_STUB_PInvoke(Microsoft.Win32.SafeHandles.SafeFileHandle, Byte*, Int32, Int32 ByRef, IntPtr) 6 04ecf9d0 052ab637 [InlinedCallFrame: 04ecf9d0] Microsoft.Win32.Win32Native.ReadFile(Microsoft.Win32.SafeHandles.SafeFileHandle, Byte*, Int32, Int32 ByRef, IntPtr) 7 04ecfa34 052ab637 System.IO.__ConsoleStream.ReadFileNative(Microsoft.Win32.SafeHandles.SafeFileHandle, Byte[], Int32, Int32, Boolean, Boolean, Int32 ByRef) 8 04ecfa68 052ab4d9 System.IO.__ConsoleStream.Read(Byte[], Int32, Int32) 9 04ecfa88 052ab3b3 System.IO.StreamReader.ReadBuffer() 10 04ecfa98 052ab178 System.IO.StreamReader.ReadLine() 11 04ecfab4 052ab129 System.IO.TextReader+SyncTextReader.ReadLine() [f:\dd\ndp\clr\src\BCL\system\io\textreader.cs @ 363] 12 04ecfac4 052aa873 System.Console.ReadLine() 13 04ecfacc 052aa425 Example_12_1_3.Person.Finalize() [E:\Visual Studio 2022\Example_12_1_3\Person.cs @ 11] 14 04ecfce8 6e4b13b4 [DebuggerU2MCatchHandlerFrame: 04ecfce8]
執行了 Person 的 Finalize()方法。為什麼不是解構函式呢?不過是一個語法糖。這個方法會被終端子執行緒持有,並被呼叫執行清理工作。千萬注意不要讓解構函式卡死,如果導致解構函式卡死,就會導致終端子執行緒卡死,所有具有解構函式的物件都無法執行清理的工作,記憶體暴漲。
1 0:000> !eeheap -gc 2 Number of GC Heaps: 1 3 generation 0 starts at 0x02f51018 4 generation 1 starts at 0x02f5100c 5 generation 2 starts at 0x02f51000 6 ephemeral segment allocation context: none 7 segment begin allocated size 8 02f50000 02f51000 02f55ff4 0x4ff4(20468) 9 Large object heap starts at 0x03f51000 10 segment begin allocated size 11 03f50000 03f51000 040265b0 0xd55b0(873904) 12 Total Size: Size: 0xda5a4 (894372) bytes. 13 ------------------------------ 14 GC Heap Size: Size: 0xda5a4 (894372) bytes.
03f51000 040265b0 紅色標註的就是大物件堆 Segment 開始和結束區間,我們通過【!dumpheap 03f51000 040265b0】檢視一下這個 LOH 裡有什麼。
1 0:000> !dumpheap 03f51000 040265b0 2 Address MT Size 3 03f51000 01165470 10 Free 4 03f51010 01165470 14 Free 5 03f51020 02dda2fc 4872 6 03f52328 01165470 14 Free 7 03f52338 02dda2fc 524 8 03f52548 01165470 14 Free 9 03f52558 02dda2fc 8172 10 03f54548 01165470 14 Free 11 03f54558 02dda2fc 4092 12 03f55558 01165470 14 Free 13 03f55568 02e07054 185012 14 03f82820 01165470 14 Free 15 03f82830 02e07054 285012 (這裡就是我們 var byte2 = new byte[285000]分配的物件) 16 03fc8188 01165470 14 Free 17 03fc8198 02e07054 385012 18 04026190 01165470 14 Free 19 040261a0 02dda2fc 1036 20 21 Statistics: 22 MT Count TotalSize Class Name 23 01165470 9 122 Free 24 02dda2fc 5 18696 System.Object[]
我們繼續【g】一下,我們的程式輸出:2、GC 已經觸發,請檢視託管堆中的 byte2。說明 byte2 物件已經被回收,也就是上面標記的物件是一個 Free 塊了。
1 0:000> !dumpheap 03f51000 040265b0 2 Address MT Size 3 03f51000 01165470 10 Free 4 03f51010 01165470 14 Free 5 03f51020 02dda2fc 4872 6 03f52328 01165470 14 Free 7 03f52338 02dda2fc 524 8 03f52548 01165470 14 Free 9 03f52558 02dda2fc 8172 10 03f54548 01165470 14 Free 11 03f54558 02dda2fc 4092 12 03f55558 01165470 14 Free 13 03f55568 02e07054 185012 14 03f82820 01165470 285046 Free(變成 Free 塊了) 15 03fc8198 02e07054 385012 16 04026190 01165470 14 Free 17 040261a0 02dda2fc 1036 18 19 Statistics: 20 MT Count TotalSize Class Name 21 02dda2fc 5 18696 System.Object[] 22 01165470 8 285140 Free 23 02e07054 2 570024 System.Byte[] 24 Total 15 objects
我們繼續【g】一下,會重新分配 byte4 = new byte[280000] 物件。我們的程式輸出:3、已分配 byte4,檢視是否 Free 塊中。我們再次檢視大物件堆,看看發生了什麼變化。
1 0:000> !dumpheap 03f51000 040265b0 2 Address MT Size 3 03f51000 01165470 10 Free 4 03f51010 01165470 14 Free 5 03f51020 02dda2fc 4872 6 03f52328 01165470 14 Free 7 03f52338 02dda2fc 524 8 03f52548 01165470 14 Free 9 03f52558 02dda2fc 8172 10 03f54548 01165470 14 Free 11 03f54558 02dda2fc 4092 12 03f55558 01165470 14 Free 13 03f55568 02e07054 185012 14 03f82820 01165470 14 Free 15 03f82830 02e07054 280012 (我們重新分配的 byte4 物件) 16 03fc6e00 01165470 5014 Free 17 03fc8198 02e07054 385012 18 04026190 01165470 14 Free 19 040261a0 02dda2fc 1036 20 21 Statistics: 22 MT Count TotalSize Class Name 23 01165470 9 5122 Free 24 02dda2fc 5 18696 System.Object[] 25 02e07054 3 850036 System.Byte[] 26 Total 17 objects