2008-10-24 25 views
109

以我的經驗看來,大多數人會告訴你,強制垃圾收集是不明智的,但在某些情況下,你正在處理的大型對象並不總是被收集到0代但內存是一個問題,可以強制收集?那裏有最佳做法嗎?在C中強制垃圾收集的最佳實踐

回答

107

最佳實踐是不強制垃圾收集。

根據MSDN:

「這是可能通過調用收集到強制進行垃圾 收集,但 大部分時間,這應該是 避免,因爲它可能會創建 性能問題。」

但是,如果你能可靠地測試你的代碼,以確認調用收集()將不會再有負面影響繼續...

試圖確保在不再需要物體時清理物體。如果您有自定義對象,請查看使用「使用語句」和IDisposable接口。

此鏈接有關於一些很好的實用的建議,以釋放內存/垃圾收集等:,而不是根0

http://msdn.microsoft.com/en-us/library/66x5fx1b.aspx

+3

另外,你可以設置不同的LatencyMode的http://msdn.microsoft.com/en-us/library/bb384202.aspx – 2011-07-08 15:18:34

13

我學會了不要試圖超越垃圾收集。有了這個說法,我只是堅持使用using關鍵字時處理非託管資源,如文件I/O或數據庫連接。

+27

編譯器?編譯器與GC有什麼關係? :) – KristoferA 2009-09-24 15:36:09

+1

沒什麼,它只編譯和優化代碼。這絕對與CLR ......或.NET無關。 – Kon 2009-10-17 16:28:03

+1

佔用大量內存的對象(如非常大的圖像)可能最終不會收集垃圾,除非您明確垃圾收集它們。我認爲這個(大對象)是OP的問題,或多或少。 – code4life 2013-02-16 17:05:23

8

不知道這是否是一種最佳做法,但是當在循環中處理大量圖像(即創建和處理大量圖形/圖像/位圖對象)時,我經常讓GC.Collect。

我想我只是在程序(大部分)閒置時才運行GC,而不是在強化循環中間運行,所以看起來像手動GC可能有意義的區域。

+0

你確定你需要這個嗎?如果需要記憶,GC *將*收集,即使您的代碼不是空閒的。 – 2008-10-24 13:56:24

+0

現在不知道它是如何在.net 3.5 SP1,但以前(1.1和我相信我測試對2.0)它確實在內存使用方面有所不同。 GC當然總是在需要時收集,但是當你只需要20時,你可能最終會浪費100M的RAM。雖然 – 2008-10-24 14:06:27

+1

當生成0達到某個閾值時觸發內存分配,但仍需要進行一些更多的測試(例如1MB),而不是當「某事物閒置」時。否則,只需簡單地分配並立即丟棄對象,就可以在循環中結束OutOfMemoryException。 – liggett78 2008-10-24 14:11:01

7

我認爲你已經列出了最佳做法,除非真的需要,否則不會使用它。我強烈建議您更詳細地查看您的代碼,如果需要,可能會首先使用分析工具來回答這些問題。

  1. 你有沒有在你的代碼的東西,是在宣告更大範圍內的項目超過需要
  2. 是內存使用量實在太高
  3. 前和使用後GC.Collect的性能比較(),看看是否它確實有幫助。
4

大對象是指在蕙(大對象堆)分配如果您'他們說他們沒有通過第0代垃圾收集,你是對的。我相信只有在完整的GC循環(第0,1和2代)發生時才收集它們。這就是說,我相信在另一方面,當你處理大型物體和內存壓力上升時,GC會更積極地調整和收集內存。

很難說是否收集和在哪些情況下。我用GC.Collect()處理了大量的控件等對話窗口/窗體之後(因爲當創建許多業務對象實例或加載大量數據時,表單及其控件最終在第2代時結束 - 沒有大的物體顯然),但實際上從長遠來看沒有發現任何積極或消極的影響。

5

假設你的程序沒有內存泄漏,對象累積並且不能在Gen 0中進行GC編輯,因爲: 1)它們被長時間引用,因此進入Gen1 & Gen2; 2)它們是大對象(> 80K),因此進入LOH(大對象堆)。而LOH不會像Gen0,Gen1 & Gen2那樣進行壓縮。

檢查「.NET Memory」的性能計數器是否可以看到1)問題確實不成問題。通常,每10個Gen0 GC將觸發1個Gen1 GC,每10個Gen1 GC將觸發1個Gen2 GC。理論上,GC1 & GC2如果GC0上沒有壓力(如果程序存儲器使用率確實接線)則決不能GC-ed。它從來沒有發生在我身上。

對於問題2),您可以檢查「.NET Memory」性能計數器以驗證LOH是否變得臃腫。如果這確實是您的問題的一個問題,也許您可​​以創建一個大對象池,因爲此博客建議http://blogs.msdn.com/yunjin/archive/2004/01/27/63642.aspx

2

還有一件事,明確觸發GC收集可能不會提高您的程序的性能。這很可能使情況變得更糟。

.NET GC的設計良好,並被調整爲自適應,這意味着它可以根據程序內存使用的「習慣」調整GC0/1/2閾值。所以,它會在一段時間後適應你的程序。一旦明確調用GC.Collect,閾值將被重置! .NET必須花時間再次適應程序的「習慣」。

我的建議總是信任.NET GC。任何內存問題的表面,檢查「.NET內存」性能計數器和診斷我自己的代碼。

27

這樣看待 - 垃圾桶在10%時扔掉廚房垃圾,或者在拿出垃圾桶之前填滿廚房垃圾更有效率嗎?

通過不讓它填滿,你正在浪費你的時間走出垃圾桶外。這與GC線程運行時發生的情況類似 - 所有託管線程在運行時都處於暫停狀態。如果我沒有弄錯,GC線程可以在多個AppDomain之間共享,所以垃圾收集會影響所有這些。

當然,你可能會遇到一種情況,你不會在垃圾桶裏隨時添加任何東西 - 比如說,如果你打算去度假。那麼,在出門之前扔掉垃圾是一個好主意。

這可能是一次迫使GC可以幫助 - 如果您的程序空閒,正在使用的內存不被垃圾收集,因爲沒有分配。

0

但是,如果你能可靠地測試你的代碼,以確認調用收集()將不會有負面影響,然後繼續前進......

恕我直言,這類似於說「如果你能證明你的程序將來永遠不會有任何錯誤,那麼繼續...「

嚴格來說,強制GC對於調試/測試的目的很有用,如果您覺得您需要在其他任何時候都這樣做,那麼要麼是您錯了,要麼是您的程序構建錯誤。解決方法是不強制GC ...

20

我認爲Rico Mariani給出的例子很好:如果應用程序的狀態發生顯着變化,觸發GC可能是合適的,例如,在文檔編輯器可能確定觸發GC當一個文件被關閉。

15

有編程是絕對少數的一般準則。一半的時候,當有人對你說'你做錯了',他們只是吐出一定量的教條。在C語言中,它曾經擔心類似自修改代碼或線程的東西,在GC語言中它會強制GC或者阻止GC運行。

如同大多數的準則和拇指(和良好的設計做法)的良好規則的情況下,也有極少數情況下它是有意義的工作,圍繞既定的規範。您必須非常確定您瞭解案件,您的案件確實需要廢除慣例,並瞭解您可能導致的風險和副作用。但也有這種情況。

編程問題多種多樣,需要靈活的方法。我已經看到了在垃圾收集語言中阻塞GC和合理觸發它的地方,而不是等待它自然發生的情況。 95%的時間,這些中的任何一個都會成爲沒有接近問題的路標。但20年中有一次,可能會有一個有效的案例。

29

最好的做法是不強制在大多數情況下,垃圾收集。(我對工作的每個系統,迫使垃圾收集,曾強調,如果解決了將已刪除強制垃圾收集需要的問題,並加快系統啓動很大。)

有一個少數情況下知道更多關於內存使用情況,然後垃圾收集器。這在多用戶應用程序或一次響應多個請求的服務中不太可能發生。

然而,在某些的批量式處理你知道那麼多的GC。例如。考慮一個應用程序。

  • 給出命令行
  • 上的文件名列表處理一個文件,然後將結果寫出來的結果文件。
  • 在處理文件,創造了很多不能收集到的文件的處理有完整的(如解析樹)相互關聯的對象
  • 不會保持它已經處理文件之間的匹配狀態。

可能能夠作出的情況下(經過縝密)測試,你應該強制執行完整的垃圾收集你有過程的每個文件之後。

另一種情況是一種服務,每隔幾分鐘就會醒來處理一些物品,並且在睡眠時不會保持任何狀態。然後在睡覺前強制完整收集可能是值得的。

我會考慮迫使 唯一一次的集合是當我知道對象的很多 已經建立最近 很少對象是目前 引用。

我寧願有一個垃圾收集API,當我可以給它關於這種類型的東西的提示,而不必強制GC我自己。

參見 「Rico Mariani's Performance Tidbits

1

不知道這是一個最好的做法...

建議:不要不確定的時候實現這個或任何東西。在事實已知時重新評估,然後在性能測試之前/之後執行驗證。

8

最近遇到的一種情況是,需要對GC.Collect()進行手動調用時,使用的是封裝在小型託管C++對象中的大型C++對象,而這些對象又是從C#訪問的。

垃圾收集器從來沒有被調用,因爲所使用的託管內存量可以忽略不計,但使用的非託管內存量非常巨大。在對象上手動調用Dispose()需要我跟蹤何時不再需要對象,而調用GC.Collect()將清除不再涉及的任何對象.....

4

我想補充一點: 調用GC.Collect()(+ WaitForPendingFinalizers())是故事的一部分。 正如其他人正確地提到的那樣,GC.COllect()是非確定性集合,由GC自己決定(CLR)。 即使您將調用添加到WaitForPendingFinalizers,它也可能不是確定性的。 從此msdn link中獲取代碼,並將對象循環迭代的代碼運行爲1或2.您將找到非確定性方法(在對象的析構函數中設置斷點)。 正確地說,只有1個(或2個)延遲對象被Wait調用時析構函數纔會被調用。()[引用要求]

如果你的代碼是處理非託管資源(例如:外部文件句柄) ,你必須實現析構函數(或終結器)。

這裏有一個有趣的例子:

注意:如果你已經嘗試過,下面的代碼將清除空氣從MSDN上面的例子。

class Program 
{  
    static void Main(string[] args) 
     { 
      SomePublisher publisher = new SomePublisher(); 

      for (int i = 0; i < 10; i++) 
      { 
       SomeSubscriber subscriber = new SomeSubscriber(publisher); 
       subscriber = null; 
      } 

      GC.Collect(); 
      GC.WaitForPendingFinalizers(); 

      Console.WriteLine(SomeSubscriber.Count.ToString()); 


      Console.ReadLine(); 
     } 
    } 

    public class SomePublisher 
    { 
     public event EventHandler SomeEvent; 
    } 

    public class SomeSubscriber 
    { 
     public static int Count; 

     public SomeSubscriber(SomePublisher publisher) 
     { 
      publisher.SomeEvent += new EventHandler(publisher_SomeEvent); 
     } 

     ~SomeSubscriber() 
     { 
      SomeSubscriber.Count++; 
     } 

     private void publisher_SomeEvent(object sender, EventArgs e) 
     { 
      // TODO: something 
      string stub = ""; 
     } 
    } 

我建議,先分析一下輸出可以再運行,然後再閱讀以下原因:

{析構函數只是隱含一旦程序結束調用。 } 爲了確定性地清理對象,必須實現IDisposable並對Dispose()進行顯式調用。這就是本質! :)