建議52:及時釋放資源
垃圾回收機制自動為我們隱式地回收了資源(垃圾回收器會自動調用終結器),那我們為什麼要主動釋放資源呢?
private void buttonOpen_Click(object sender,EventArgs e) { FileStream fileStream = new FileStream(@"c:\test.txt",FileMode.Open); } private void buttonGC_Click(object sender,EventArgs e) { System.GC.Collect(); }
這是一個WinForm窗體程序的例子,在這個示例中,單擊一個按鈕負責打開一個文件,單擊另一個按鈕負責回收說有“代”(代的概念會在下文詳細指出)的垃圾。如果連續兩次單擊打開文件的按鈕,系統就會報錯:
IOException:文件“c:\test.txt”正由另外一進程使用,因此該進程無法訪問此文件。
如果先單擊打開文件的按鈕,繼而再單擊清理按鈕,則運行正常。
現在來分析:在打開文件的方法中,方法執行完畢後,由於局部變量fileStream在程序中已經沒有任何地方引用了,所以它會在下一次垃圾回收時被運行時標記為垃圾。那麼,什麼時候會進行下一次垃圾回收呢,後者說垃圾回收器什麼時候才開始真正進行回收工作呢?微軟官方的解釋是,當滿足以下條件之一時將發生垃圾回收:
不及時釋放資源是對系統的一種極大的浪費,這種浪費還會干擾程序的正常運行(如在本實例中,由於它始終占著文件資源,導致我們不能再次使用這個文件資源了)。
若果類型本身繼承了IDisposable接口,垃圾回收機制雖然會自動幫我們釋放資源,但是這個過程卻延長了,因為它不是在一次回收中完成所有的清理工作。本例中因為fileStream繼承了IDisposable接口,故第一次進行垃圾回收時,垃圾回收器會調用fileStream的終結器,然後等待下一次的垃圾回收,這時fileStream對象才有可能被真正的回收掉。
我們來改進這個程序:
private void buttonOpen_Click(object sender,EventArgs e) { FileStream fileStream = new FileStream(@"c:\test.txt",FileMode.Open); fileStream.Dispose(); }
但是如果第一行代碼出現異常,就永遠執行不了filsStream.Dispose()了。再次改進:
private void buttonOpen_Click(object sender,EventArgs e) { try { FileStream fileStream = new FileStream(@"c:\test.txt",FileMode.Open); } finally { fileStream.Dispose(); } }
使用語法糖“using"關鍵字進一步簡化:
private void buttonOpen_Click(object sender,EventArgs e) { using(FileStream fileStream = new FileStream(@"c:\test.txt",FileMode.Open)) { } }
關於“代數”:一共分為3代:第0代、第1代、第2代。
當條件得到滿足時,垃圾回收將在特定代上發生。 回收某個代意味著回收此代中的對象及其所有更年輕的代。 第 2 代垃圾回收也稱為完整垃圾回收,因為它回收所有代上的所有對象(即,托管堆中的所有對象)。
幸存和提升
垃圾回收中未回收的對象也稱為幸存者,並會被提升到下一代。 在第 0 代垃圾回收中幸存的對象將被提升到第 1 代;在第 1 代垃圾回收中幸存的對象將被提升到第 2 代;而在第 2 代垃圾回收中幸存的對象將仍為第 2 代。
當垃圾回收器檢測到某個代中的幸存率很高時,它會增加該代的分配阈值,因此下一次回收將會獲取一個非常大的回收內存。 CLR 會在以下兩個優先級別之前進行平衡:不允許應用程序的工作集獲取太大內存以及不允許垃圾回收花費太多時間。
暫時代和暫時段
因為第 0 代和第 1 代中的對象的生存期較短,因此,這些代被稱為暫時代。
暫時代必須在稱為暫時段的內存段中進行分配。 垃圾回收器獲取的每個新段將成為新的暫時段,並包含在第 0 代垃圾回收中幸存的對象。 舊的暫時段將成為新的第 2 代段。
更多內容可參考MSDN:垃圾回收的基礎
轉自:《編寫高質量代碼改善C#程序的157個建議》陸敏技