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

歡迎訪問 生活随笔!

生活随笔

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

编程问答

从GC的SuppressFinalize方法带你深刻认识Finalize底层运行机制

發布時間:2023/12/4 编程问答 29 豆豆
生活随笔 收集整理的這篇文章主要介紹了 从GC的SuppressFinalize方法带你深刻认识Finalize底层运行机制 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

如果你經常看開源項目的源碼,你會發現很多Dispose方法中都有這么一句代碼:?GC.SuppressFinalize(this);?,看過一兩次可能無所謂,看多了就來了興趣,這篇就跟大家聊一聊。

一:背景

1. 在哪發現的

相信現在Mysql在.Net領域中鋪的面越來越廣了,C#對接MySql的MySql.Data類庫的代碼大家可以研究研究,幾乎所有操作數據庫的幾大對象:MySqlConnection,MySqlCommand,MySqlDataReader以及內部的Driver都存在?GC.SuppressFinalize(this)代碼。

public sealed class MySqlConnection : DbConnection, ICloneable {public new void Dispose(){Dispose(disposing: true);GC.SuppressFinalize(this);} }public sealed class MySqlCommand : DbCommand, IDisposable, ICloneable {public new void Dispose(){Dispose(disposing: true);GC.SuppressFinalize(this);} }

2. GC.SuppressFinalize 場景在哪里

先看一下官方對這個方法的解釋,如下所示:

//// Summary:// Requests that the common language runtime not call the finalizer for the specified// object.//// Parameters:// obj:// The object whose finalizer must not be executed.//// Exceptions:// T:System.ArgumentNullException:// obj is null.[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)][SecuritySafeCritical]public static void SuppressFinalize(object obj);

意思就是說: 請求 CLR 不要調用指定對象的終結器,如果你對終結器的前置基礎知識不足,那這句話肯定不是很明白,既然都執行了Dispose,說明非托管資源都被釋放了,怎么還壓制CLR不要調用Finalize呢?刪掉和不刪掉這句代碼有沒有什么嚴重的后果,GC類的方法誰也不敢動哈。。。為了徹底講清楚,有必要說一下Finalize整個原理。

二:資源管理

我們都知道C#是一門托管語言,它的好處就是不需要程序員去關心內存的分配和釋放,由CLR統一管理,這樣編程門檻大大降低,天下攘攘皆為利來,速成系的程序員就越來越多~

1. 對托管資源和非托管資源理解

<1> 托管資源

這個很好理解,你在C#中使用的值類型,引用類型都是統一受CLR分配和GC清理。

<2> 非托管資源

在實際業務開發中,我們的代碼不可能不與外界資源打交道,比如說文件系統,外部網站,數據庫等等,就拿寫入文件的StreamWriter舉例,如下代碼:

public static void Main(string[] args){StreamWriter sw = new StreamWriter("xxx.txt");sw.WriteLine("....");}

為什么能夠寫入文件?那是因為我們的代碼是請求windows底層的Win32 Api幫忙寫入的,這就有意思了,因為這個場景有第三者介入,sw是引用類型受CLR管理,win32 api屬于外部資源和.Net一點關系都沒有,如果你在用完sw之后沒有調用close方法的話,當某個時候GC回收了托管堆上的sw后,這給被打開的win32 api文件句柄再也沒有人可以釋放了,資源就泄露了,如果沒看懂,我畫張圖:

三:頭疼的非托管資源解決方案

1. 使用析構函數

很多時候程序員就是在使用完類之后因為種種原因忘記了手動執行Close方法造成了資源泄露,那有沒有一種機制可以在GC回收堆對象的時候回調我的一個自定義方法呢?如果能實現就????????了,這樣我就可以在自定義方法中做全局的控制。

其實這個自定義方法就是析構函數,接下來我把上面的?StreamWriter?改造下,將?Close()?方法放置在析構函數中,先看一下代碼:

public class Program{public static void Main(string[] args){MyStreamWriter sw = new MyStreamWriter("xxx.txt");sw.WriteLine("....");GC.Collect();Console.ReadLine();}}public class MyStreamWriter : StreamWriter{public MyStreamWriter(string filename) : base(filename) { }~MyStreamWriter(){Console.WriteLine("嘿嘿,忘記調用Close方法了吧!我來幫你");base.Dispose(false);Console.WriteLine("非托管資源已經幫你釋放啦,不要操心了哈");}}--------- output -----------嘿嘿,忘記調用Close方法了吧!我來幫你 非托管資源已經幫你釋放啦,不要操心了哈

四:析構函數被執行的底層分析

讓GC來通知我的回調方法這本身就很????????,但仔細想想,在垃圾回收時,CLR不是將所有線程都掛起了嗎?怎么還有活動的線程,而且這個線程是來自哪里?線程池嗎?好,先從理論跟和大家分析一下,析構函數在CLR層面稱為Finalize方法,為了方便后面通過windbg去驗證,這里統一都叫Finalize方法,提前告知。

1. 原理步驟

<1> CLR在啟動時會構建一個“Finalize全局數組”和“待處理Finalize數組” ,所有定義Finalize方法的類,它的引用地址全部額外再灌到“Finalize全局數組”中。

<2> CLR啟動一個專門的“Finalize線程”讓其全權監視“待處理Finalize數組”。

<3> GC在開啟清理前標記對象引用時,如發現某一個對象只有一個在Finalize數組中的引用,說明此對象是垃圾了,CLR將該對象地址轉移到另外一個 “待處理Finalize” 數組中。

<4> 由于該對象還存在引用,所以GC放了一馬,然后“Finalize線程”監視到了 “待處理Finalize數組” 新增的對象,取出該對象并執行該對象的Finalize方法。

<5> 由于是破壞性取出,此時該對象再無任何引用,下次GC啟動時就會清理出去。

看文字有點繞,我畫一張圖幫大家理解下。

2. windbg驗證

<1> 修改Main代碼如下,抓一下dump文件看看 MyStreamWriter是否在Finalize全局數組中。

public static void Main(string[] args){MyStreamWriter sw = new MyStreamWriter("xxx.txt");sw.WriteLine("....");Console.ReadLine();}``` C#0:000> !FinalizeQueue SyncBlocks to be cleaned up: 0 Free-Threaded Interfaces to be released: 0 MTA Interfaces to be released: 0 STA Interfaces to be released: 0 ---------------------------------- generation 0 has 13 finalizable objects (0000018c2a9b7a80->0000018c2a9b7ae8)generation 1 has 0 finalizable objects (0000018c2a9b7a80->0000018c2a9b7a80)generation 2 has 0 finalizable objects (0000018c2a9b7a80->0000018c2a9b7a80)Ready for finalization 0 objects (0000018c2a9b7ae8->0000018c2a9b7ae8)Statistics for all finalizable objects (including all objects ready for finalization):MT Count TotalSize Class Name00007ff8e7afb2a8 1 32 System.Runtime.InteropServices.NativeBuffer+EmptySafeHandle00007ff8e7a94078 1 32 Microsoft.Win32.SafeHandles.SafePEFileHandle00007ff8e7a843b0 1 32 Microsoft.Win32.SafeHandles.SafeFileMappingHandle00007ff8e7a84320 1 32 Microsoft.Win32.SafeHandles.SafeViewOfFileHandle00007ff8e7b001b8 1 40 System.Runtime.InteropServices.SafeHeapHandleCache00007ff8e7ad6df0 1 40 System.Runtime.InteropServices.SafeHeapHandle00007ff8e7b133d0 2 64 Microsoft.Win32.SafeHandles.SafeRegistryHandle00007ff8e7a995d0 2 64 Microsoft.Win32.SafeHandles.SafeFileHandle00007ff8e7a93b48 1 64 System.Threading.ReaderWriterLock00007ff8e7b14d38 1 104 System.IO.FileStream00007ff889d45b18 1 112 ConsoleApp2.MyStreamWriterTotal 13 objects

很驚喜的看到?MyStreamWriter?就在其中,符合圖中所示。

<2> 查看是否有專門的 “Finalize線程” ,可以通過?!threads?命令查看。

0:000> !threads ThreadCount: 2 UnstartedThread: 0 BackgroundThread: 1 PendingThread: 0 DeadThread: 0 Hosted Runtime: noLock ID OSID ThreadOBJ State GC Mode GC Alloc Context Domain Count Apt Exception0 1 bf4 0000018c2a990f00 2a020 Preemptive 0000018C2C429168:0000018C2C429FD0 0000018c2a965220 1 MTA6 2 44f4 0000018c2a9b9450 2b220 Preemptive 0000000000000000:0000000000000000 0000018c2a965220 0 MTA (Finalizer)

看到沒,線程2標記了?MTA (Finalizer), 說明果然有執行Finalizer方法的專有線程。????????????

<3> 由于水平有限,不知道怎么去看 “待處理Finalize數組”,所以只能驗證等GC回收之后,看下 “Finalize全局數組”中是否還存在MyStreamWriter即可。

public static void Main(string[] args){MyStreamWriter sw = new MyStreamWriter("xxx.txt");sw.WriteLine("....");GC.Collect();Console.ReadLine();}------- output ---------嘿嘿,忘記調用Close方法了吧! 我來幫你 非托管資源已經幫你釋放啦,不要操心了哈0:000> !FinalizeQueue SyncBlocks to be cleaned up: 0 Free-Threaded Interfaces to be released: 0 MTA Interfaces to be released: 0 STA Interfaces to be released: 0 ---------------------------------- generation 0 has 5 finalizable objects (0000021e8051a798->0000021e8051a7c0)generation 1 has 5 finalizable objects (0000021e8051a770->0000021e8051a798)generation 2 has 0 finalizable objects (0000021e8051a770->0000021e8051a770)Ready for finalization 0 objects (0000021e8051a7c0->0000021e8051a7c0)Statistics for all finalizable objects (including all objects ready for finalization):MT Count TotalSize Class Name00007ff8e7afb2a8 1 32 System.Runtime.InteropServices.NativeBuffer+EmptySafeHandle00007ff8e7a94078 1 32 Microsoft.Win32.SafeHandles.SafePEFileHandle00007ff8e7a843b0 1 32 Microsoft.Win32.SafeHandles.SafeFileMappingHandle00007ff8e7a84320 1 32 Microsoft.Win32.SafeHandles.SafeViewOfFileHandle00007ff8e7b001b8 1 40 System.Runtime.InteropServices.SafeHeapHandleCache00007ff8e7ad6df0 1 40 System.Runtime.InteropServices.SafeHeapHandle00007ff8e7a995d0 2 64 Microsoft.Win32.SafeHandles.SafeFileHandle00007ff8e7a93b48 1 64 System.Threading.ReaderWriterLock00007ff8e7a96a10 1 96 System.Threading.ThreadTotal 10 objects

可以看到這時候 “全局數組” 沒有引用了,再看一下托管堆是否還存在 MyStreamWriter以及線程棧中是否還有對象引用地址。

0:000> !dumpheap Address MT Size 00007ff889d25b00 1 112 ConsoleApp2.MyStreamWriterTotal 423 objects0:000> !clrstack -l OS Thread Id: 0x1b00 (0)Child SP IP Call Site 0000007ecdffe9e0 00007ff8e88c20cc System.IO.__ConsoleStream.ReadFileNative(Microsoft.Win32.SafeHandles.SafeFileHandle, Byte[], Int32, Int32, Boolean, Boolean, Int32 ByRef)LOCALS:<no data><no data><no data><no data><no data><no data> 0000007ecdffea70 00007ff8e88c1fd5 System.IO.__ConsoleStream.Read(Byte[], Int32, Int32)LOCALS:<no data><no data> 0000007ecdffead0 00007ff8e80770f4 System.IO.StreamReader.ReadBuffer()LOCALS:<no data><no data> 0000007ecdffeb20 00007ff8e8077593 System.IO.StreamReader.ReadLine()LOCALS:<no data><no data><no data><no data> 0000007ecdffeb80 00007ff8e8a68b0d System.IO.TextReader+SyncTextReader.ReadLine() 0000007ecdffebe0 00007ff8e8860d98 System.Console.ReadLine() 0000007ecdffec10 00007ff889e30959 ConsoleApp2.Program.Main(System.String[]) 0000007ecdffeea8 00007ff8e9396c93 [GCFrame: 0000007ecdffeea8]

可以看到MyStreamWriter還是存在于托管堆,但是線程棧已再無它的引用地址,就這樣告別了全世界,下次GC啟動就要被徹底運走了。

五:回頭再看 SuppressFinalize

如果你看懂了上面?Finalize?原理,再來看?SuppressFinalize的解釋:‘請求 CLR 不要調用指定對象的終結器’。

就是說當你手動調用Dispose或者Close方法釋放了非托管資源后,通過此方法強制告訴CLR不要再觸發我的析構函數了,否則再執行析構函數相當于又做了一次清理非托管資源的操作,造成未知風險。

好了,本篇就說這么多,希望你對有幫助。

總結

以上是生活随笔為你收集整理的从GC的SuppressFinalize方法带你深刻认识Finalize底层运行机制的全部內容,希望文章能夠幫你解決所遇到的問題。

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