閒話資源管理

對於 .NET 開發人員來說程序的資源管理是必不可少的,要開發出一款優秀的應用就必須明白 .NET 的資源管理機制。在 .NET 中垃圾回收器 GC 幫助我們管理託管資源,在開發時我們不需要過多的關注大多數內存問題(例如:內存泄漏、dangling pointer)。

零、託管資源

在 .NET 中一個經典的資源管理的例子就是 GC 對循環引用的管理。 GC 的 Mark and Compact 算法會快速的檢測出由簡單或複雜的關係網所形成的循環引用,並把所有不可達的對象是爲一個整體從內存中清理出去。 GC 會檢測出和應用程序根對象沒有任何通路相連的對象,然後判定這些對象爲不可達對象,接着將這些對象從內存中清理出去,最後 GC 將會壓縮託管堆把其中活動的對象放在一起,把空閒的內存放在一起形成連續的內存區域。簡單地說就是當應用程序不再使用(引用)某個對象時 GC 就會認定這個對象是可回收的。

一、非託管資源

前面我們一直在說託管資源,託管資源由 GC 負責因此我們不需要干涉太多,我們需要重點干涉的就是非託管資源。非託管資源包括數據庫連接、 COM 對象、 GDI+ 對象以及其他系統對象,這種資源我們如果不加以干涉的話會造成內存大量佔用,進而程序崩潰操作系統崩潰。
在 .NET 中針對非託管資源的控制一共有兩種方法:一種是 finalizer 另一種是 IDisposable 。前者的機制存在很多缺陷,因此我們在開發中一般會使用 IDisposable 來釋放非託管資源。下面我們來詳細的講解一下爲何要選擇後者而不是前者。
一般情況下如果對象存在 finalizer 時 GC 會在判定該對象爲垃圾後擇機調用 finalizer 。但是 finalizer 並不是及時執行的,也就是說程序在退出臨界區域時,相關的對象(這裏的對象指的時方法、類、窗體等)並不是一執行完就立馬退出,而是會在內存中停留一段時間。因此 finalizer 只能保證非託管資源最終可以被釋放,但是不保證在何時釋放。並且大量依賴 finalizer 會降低程序的性能,這是因爲垃圾回收器需要執行更多的工作才能最終終結這些資源。 GC 清理帶有 finalizer 的垃圾對象是這麼做的:首先 GC 會在每個週期內把包含 finalizer 但還沒有執行的對象放在隊列中,在下一個週期裏 GC 會清理掉已經執行過 finalizer 的對象,這其中執行對象 finalizer 的線程並不是 GC 所在的線程。
在這裏需要注意的是具有 finalizer 的對象並不是只存在與一個週期中,而是有可能存在於多個週期中。.NET 爲優化垃圾回收工作,特意定義了 generation (世代) 這個概念,.NET 中一共包含 3個世代,第一次垃圾回收之後創建出來的對象叫做 0 代對象,再一次執行垃圾回收後依然存在的對象叫做 1 代對象,經過兩次或多次依然存在的對象則叫做 2 代對象。然後世代的存在給具有 finalizer 的對象帶來了我發及時釋放的問題,因爲 GC 在每次執行週期都會判斷 0 代對象是不是垃圾,每執行 10 個週期就會判斷一次 1 代對象是不是垃圾,到了 2 代對象這裏則會執行 100 個週期纔會判斷一次,因此具有 finalizer 的對象最少需要 GC 執行 10 個週期纔會清理,而不具有 finalizer 的對象最多 9 個週期就會被清理。

二、小結

這一篇文章就這麼短,通過這篇文章我們需要注意在需要使用非託管資源的時候優先選擇 IDisposable 接口,除非是必須使用 finalizer 。後面的文章我將詳細講解怎麼樣的編碼才能算是具有良好的資源管理的編碼。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章