標籤:rgs 分配 改善 記憶體 參考 工作 void 訪問 str
建議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在程式中已經沒有任何地方引用了,所以它會在下一次記憶體回收時被運行時標記為垃圾。那麼,什麼時候會進行下一次記憶體回收呢,後者說記憶體回收行程什麼時候才開始真正進行回收工作呢?微軟官方的解釋是,當滿足以下條件之一時將發生記憶體回收:
- 系統具有低的實體記憶體。
- 由託管堆上已指派的對象使用的記憶體超出了可接受的閾值。 這意味著可接受的記憶體使用量的閾值已超過託管堆。 隨著進程的運行,此閾值會不斷地進行調整。
- 調用 GC.Collect 方法。 幾乎在所有情況下,您都不必調用此方法,因為記憶體回收行程會持續運行。 此方法主要用於特殊情況和測試。
不及時釋放資源是對系統的一種極大的浪費,這種浪費還會干擾程式的正常運行(如在本執行個體中,由於它始終佔著檔案資源,導致我們不能再次使用這個檔案資源了)。
若果類型本身繼承了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代。
- 第 0 代:這是最年輕的代,其中包含短生存期對象。 短生存期對象的一個樣本是臨時變數。 記憶體回收最常發生在此代中。新分配的對象構成新一代的對象並且為隱式的第 0 代回收,除非它們是大對象,在這種情況下,它們將進入第 2 代回收中的大對象堆。大多數對象通過第 0 代中的記憶體回收進行回收,不會保留到下一代。
- 第 1 代:這一代包含短生存期對象並用作短生存期對象和長生存期對象之間的緩衝區。
- 第 2 代:這一代包含長生存期對象。 長生存期對象的一個樣本是伺服器應用程式中的一個包含在進程期間處於活動狀態的待用資料的對象。
當條件得到滿足時,記憶體回收將在特定代上發生。 回收某個代意味著回收此代中的對象及其所有更年輕的代。 第 2 代記憶體回收也稱為完整記憶體回收,因為它回收所有代上的所有對象(即,託管堆中的所有對象)。
倖存和提升
記憶體回收中未回收的對象也稱為倖存者,並會被提升到下一代。 在第 0 代記憶體回收中倖存的對象將被提升到第 1 代;在第 1 代記憶體回收中倖存的對象將被提升到第 2 代;而在第 2 代記憶體回收中倖存的對象將仍為第 2 代。
當記憶體回收行程檢測到某個代中的倖存率很高時,它會增加該代的分配閾值,因此下一次回收將會擷取一個非常大的回收記憶體。 CLR 會在以下兩個優先順序別之前進行平衡:不允許應用程式的工作集擷取太大記憶體以及不允許記憶體回收花費太多時間。
暫時代和暫時段
因為第 0 代和第 1 代中的對象的生存期較短,因此,這些代被稱為暫時代。
暫時代必須在稱為暫時段的記憶體段中進行分配。 記憶體回收行程擷取的每個新段將成為新的暫時段,並包含在第 0 代記憶體回收中倖存的對象。 舊的暫時段將成為新的第 2 代段。
更多內容可參考MSDN:記憶體回收的基礎
轉自:《編寫高品質代碼改善C#程式的157個建議》陸敏技
【轉】編寫高品質代碼改善C#程式的157個建議——建議52:及時釋放資源