.NET對(duì)象清理:垃圾回收和資源清理
原創(chuàng)【51CTO.com原創(chuàng)稿件】在 .NET 中垃圾回收和資源清理是重中之重的內(nèi)容,也是所有程序都必須用到的機(jī)制,但是有很大一部分開發(fā)人員并不知道垃圾回收和資源清理的原理。那么,我將通過這篇文章向各位讀者詳細(xì)講解一下垃圾回收和資源清理。
一、垃圾回收
.NET中垃圾回收是運(yùn)行時(shí)的核心功能,它的作用是回收不再被引用的對(duì)象所占用的內(nèi)存。這里我們要注意垃圾回收器只回收內(nèi)存資源而不處理其他資源。此外垃圾回收器是根據(jù)是否存在任何引用來決定要清理那些東西,也就是說垃圾回收器處理的是不被引用的引用對(duì)象,并且只能回收堆上的內(nèi)存。
- 
    
簡(jiǎn)述 在 .NET 中垃圾回收的很多細(xì)節(jié)都和 CLI 有關(guān),我們常用的 Microsoft.NET 框架中實(shí)現(xiàn)垃圾回收的算法是 mark-and-compact 算法 。當(dāng)每次一次垃圾回收周期開始時(shí),它會(huì)查找對(duì)象的所有根引用(一般來說根引用來自靜態(tài)變量、CPU寄存器和局部變量或參數(shù)實(shí)例的任何引用)。基于查找到的所有根引用,垃圾回收器就可以遍歷每個(gè)根引用標(biāo)識(shí)的樹形結(jié)構(gòu),并遞歸確定每個(gè)根引用指向的對(duì)象,進(jìn)而識(shí)別出所有可達(dá)對(duì)象。
當(dāng)執(zhí)行垃圾回收時(shí),垃圾回收器會(huì)將所有可達(dá)對(duì)象一個(gè)挨一個(gè)的放在一起,這樣就可以覆蓋不可達(dá)對(duì)象所占用的內(nèi)存。為了定位和移動(dòng)可達(dá)對(duì)象,進(jìn)程中所有托管線程都會(huì)在垃圾回收期間暫停運(yùn)行,這樣就可以保證垃圾回收器在運(yùn)行期間維持狀態(tài)一致性。雖然這么做會(huì)造成應(yīng)用程序短暫停止工作,但是一般來說只要垃圾回收周期不是特別長(zhǎng),這個(gè)短暫的停止工作是很難發(fā)覺的。在我們開發(fā)時(shí)有時(shí)可能不希望在運(yùn)行一些代碼段時(shí)執(zhí)行垃圾回收,這時(shí)我們可以在代碼段之前使用 System.GC 對(duì)象所包含的 Collect 方法來讓垃圾回收暫時(shí)跳過這些代碼。當(dāng)然這么做是不會(huì)阻止垃圾回收運(yùn)行的,只是減少了這部分代碼可能被回收的概率,但是這里有一個(gè)前提條件:代碼段執(zhí)行期間不會(huì)發(fā)生內(nèi)存被大量消耗使用的情況。在 .NET 中垃圾回收有一個(gè)特別的地方,就是并非所有的垃圾都會(huì)在一個(gè)垃圾回收周期內(nèi)被回收。這是為什么呢?因?yàn)樵?.NET 垃圾回收器中有一個(gè)名字叫 generation 的概念,翻譯成中文就是 代 。它會(huì)清理那些生存時(shí)間較短的對(duì)象,那些在一次垃圾回收周期中存活下來的對(duì)象會(huì)降低清理頻率。也就是說當(dāng)一個(gè)對(duì)象在一次垃圾回收周期中存活下來,那么它將會(huì)被移動(dòng)到下一代中,如果它又在一次垃圾回收周期中存活下來,那么它將被移動(dòng)到最后一代,也就是第二代(為什么是第二代呢?因?yàn)?.NET 垃圾回收機(jī)制中代是從 0 開始的),第零代清理速度最快,第二代清理速度最慢。 - 
    
弱引用 弱引用這個(gè)名詞很少有開發(fā)人員聽過,所謂的弱引用是為創(chuàng)建起來開銷很高并且維護(hù)成本也很大的對(duì)象而設(shè)計(jì)的。它不阻止垃圾回收器對(duì)對(duì)象的回收,但會(huì)維持一個(gè)引用,進(jìn)而可以在被垃圾回收器回收之前可以重用。例如我們從數(shù)據(jù)庫(kù)中查詢一個(gè)龐大的數(shù)據(jù)列表向用戶展示,如果沒有使用弱引用當(dāng)用戶關(guān)閉了這個(gè)列表,那么垃圾回收器就有很大可能將它回收,那么當(dāng)用戶再次查看這個(gè)列表時(shí),程序又需要從數(shù)據(jù)庫(kù)查詢并加載出來,這種操作成本是很高昂的。如果使用了如引用,每次請(qǐng)求列表時(shí)代碼首先檢查列表是否被清除,如果沒有被清除就直接將列表展示給用戶,如果被清除了就從數(shù)據(jù)庫(kù)查詢并展示給用戶,這就相當(dāng)于對(duì)象在內(nèi)存中進(jìn)行了緩存。如果開發(fā)人員認(rèn)為對(duì)象應(yīng)該進(jìn)行弱引用,那么就可以把這個(gè)對(duì)象賦值給 System.WeakReference 。下面我們來看一個(gè)弱認(rèn)證的簡(jiǎn)單例子:
- WeakReference Data;
 - public FileStream Date()
 - {
 - FileStream fs= (FileStream)Data.Target;
 - if(data!=null)
 - {
 - return data;
 - }
 - // more code
 - Data.Target=data;
 - return data;
 - }
 
上面的代碼是一個(gè)標(biāo)準(zhǔn)的創(chuàng)建弱引用的代碼,我們可以看到在代碼中對(duì)變量 data 進(jìn)行了 null 判斷,我們可以通過這個(gè)判斷來檢查垃圾回收器是否將其回收。這里還有一個(gè)關(guān)鍵代碼 FileStream fs= (FileStream)Data.Target; 這里將弱引用賦值給了強(qiáng)引用,這樣可以避免在檢查 null 后和訪問數(shù)據(jù)前,發(fā)生垃圾回收器回收弱引用。
 
二、資源清理
在前面一小節(jié)開頭我們說過垃圾回收之回收內(nèi)存中的對(duì)象,那么如果我們需要回收其他資源呢,例如數(shù)據(jù)庫(kù)連接、句柄、外部設(shè)備。這時(shí)我們就需要用到資源清理。
終結(jié)器 終結(jié)器是一個(gè)允許開發(fā)人員通過代碼來清理類資源的東西。終結(jié)器最大的特征是它不能在代碼中顯式調(diào)用,只有垃圾回收器負(fù)責(zé)對(duì)對(duì)象的實(shí)例調(diào)用終結(jié)器,因此開發(fā)人員無(wú)法在編譯時(shí)確定終結(jié)器在何時(shí)執(zhí)行,只能夠確定終結(jié)器時(shí)對(duì)象中最后一次被調(diào)用的地方。 終結(jié)器的定義也很簡(jiǎn)單,只需要在類名之前加一個(gè) ~ 符號(hào)即可。
- class Demo
 - {
 - public Demo(string name)
 - {
 - //more code
 - }
 - ~Demo()
 - {
 - Close();
 - }
 - public void Close()
 - {
 - //more code
 - }
 - //more code
 - }
 
上述代碼我們就定義了一個(gè)簡(jiǎn)單的終結(jié)器,我們定義終結(jié)器的時(shí)候需要注意以下四點(diǎn):
因?yàn)榻K結(jié)器是在自己的線程中執(zhí)行的,因此如果終結(jié)器中存在一個(gè)未處理的異常就會(huì)很難診斷發(fā)現(xiàn),因?yàn)樵斐僧惓5那闆r并不清晰透明。所以我們必須避免在終結(jié)器中引發(fā)異常。
- 終結(jié)器是不允許傳遞任何參數(shù)的,也不能重載它;
 - 因?yàn)樗潜焕厥掌魉{(diào)用,因此給終結(jié)器加上訪問修飾符是毫無(wú)意義的;
 - 如果父類中存在終結(jié)器,那么將會(huì)作為子類終結(jié)器的一部分被自動(dòng)調(diào)用;
 - 終結(jié)器必須顯示的釋放資源。
 
- 
    
using 雖然終結(jié)器可以幫助我們?cè)谕涳@式調(diào)用必要清理代碼的時(shí)候執(zhí)行清理,但是因?yàn)榻K結(jié)器的運(yùn)行存在不確定性,因此我們只能將它作為備用機(jī)制。正常情況下我們可以使用 using 。 C# 中的 IDisposable 接口的 Dispose 方法為我們提供了實(shí)現(xiàn)細(xì)節(jié)。我們先來看一段代碼。
- class Demo
 - {
 - MyFileStream fs =new myFileStram();
 - //more code
 - fs.Dispose();
 - //more code
 - }
 - class MyFileStream:IDisposable
 - {
 - public MyFileStream(string path)
 - {
 - //more code
 - }
 - //more code
 - ~MyFileStream
 - {
 - Dispose(false);
 - }
 - public void Close()
 - {
 - Dispose();
 - }
 - public void Dispose()
 - {
 - Dispose(true);
 - System.GC.SuppressFinalize();
 - }
 - public void Dispose(bool para)
 - {
 - // more code
 - }
 - }
 
上述代碼中我們顯式調(diào)用了 MyFileStream 類的 Dispose 方法。 Dispose 方法主要用來清理已經(jīng)用過的資源,但是這里存在一個(gè)問題,當(dāng)我們調(diào)用 Dispose 方法時(shí)有可能會(huì)發(fā)生異常,這時(shí)我們就無(wú)法正確調(diào)用 Dispose 方法了,為了避免這個(gè)問題我們需要加入 try..finally 塊。但是我們無(wú)法保證開發(fā)人員每次都會(huì)寫 try...finally ,這時(shí)我們可以使用 C# 提供的 using 語(yǔ)句,我們將上面的調(diào)用代碼修改一下:
- class Demo
 - {
 - using(MyFileStream fs =new myFileStram())
 - {
 - //more code
 - }
 - }
 
這段代碼最終生成的 CIL 代碼和使用 try...finally 塊生成的代碼完全一樣。
 - 
    
垃圾回收、終結(jié)和 IDisposable 在上一小節(jié)的代碼中我們看到在 Dispose 方法中我們調(diào)用了 System.GC.SuppressFinalize(); ,它的作用是從終結(jié)隊(duì)列中移除 MyFileStream 實(shí)例。因?yàn)樗星謇矶荚贒ispose 方法中完成了,而不是等著終結(jié)器執(zhí)行。如果不調(diào)用 System.GC.SuppressFinalize() 方法實(shí)例將會(huì)一直在終結(jié)隊(duì)列中,只有當(dāng)終結(jié)方法被調(diào)用之后才能在垃圾回收器中被回收,那么這就造成了托管資源垃圾回收處理時(shí)間的延遲。 Dispose 方法中調(diào)用了 Dispose(bool para) 方法,在這個(gè)方法里我們可以清理資源并阻止終結(jié)器。其次,我們定義了 Close 方法來調(diào)用 Dispose(bool para) 方法,這樣終結(jié)器就可以調(diào)用 Dispose(bool para) 方法來關(guān)閉釋放資源。針對(duì)前一小結(jié)的代碼需要有如下幾點(diǎn)注意:
在某些特殊情況下垃圾回收的對(duì)象有可能會(huì)被無(wú)意中重新引用一個(gè)待終結(jié)的對(duì)象。這樣,被重新引用的對(duì)象就不再是不可訪問的,所以不能當(dāng)作垃圾被回收掉。假如對(duì)象的終結(jié)方法已經(jīng)運(yùn)行,那么除非顯式標(biāo)記為要進(jìn)行終結(jié),否則終結(jié)方法不一定會(huì)再次運(yùn)行。
 
- 只針對(duì)開銷大,成本高的對(duì)象實(shí)現(xiàn)終結(jié)器;
 - 如果類存在終結(jié)器那么就必須實(shí)現(xiàn) IDisposable ;
 - 不要在終結(jié)器中拋出異常;
 - 在 Dispose 方法中必須調(diào)用 System.GC.SuppressFinalize ;
 - 保證 Dispose 可以被重用;
 - 保證 Dispose 方法的簡(jiǎn)單性;
 - 不能在終結(jié)器中調(diào)用未被終結(jié)的其他對(duì)象;
 - 如果父類存在終結(jié)器,再重寫時(shí)必須調(diào)用父類終結(jié)器;
 - 調(diào)用 Dispose 方法之后,將對(duì)象設(shè)為不可用。
 
三、小結(jié)
作者介紹:
朱鋼,筆名喵叔,國(guó)內(nèi)某技術(shù)博客認(rèn)證專家,.NET高級(jí)開發(fā)工程師,7年一線開發(fā)經(jīng)驗(yàn),參與過電子政務(wù)系統(tǒng)和AI客服系統(tǒng)的開發(fā),以及互聯(lián)網(wǎng)招聘網(wǎng)站的架構(gòu)設(shè)計(jì),目前就職于一家初創(chuàng)公司,從事企業(yè)級(jí)安全監(jiān)控系統(tǒng)的開發(fā)。
【51CTO原創(chuàng)稿件,合作站點(diǎn)轉(zhuǎn)載請(qǐng)注明原文作者和出處為51CTO.com】















 
 
 






 
 
 
 