2011-05-19 90 views
7

我的任務是改進一段代碼,以任何我認爲合適的方式生成大量報告。生成報告時診斷.NET OutOfMemoryException

有生成(對於每個數據庫的「部分」)大約10個相同的報告,以及用於它們的代碼是類似於:

GeneratePurchaseReport(Country.France, ProductType.Chair); 
GC.Collect(); 
GeneratePurchaseReport(Country.France, ProductType.Table); 
GC.Collect(); 
GeneratePurchaseReport(Country.Italy, ProductType.Chair); 
GC.Collect(); 
GeneratePurchaseReport(Country.Italy, ProductType.Table); 
GC.Collect(); 

如果刪除那些GC.Collect()呼叫,報告服務崩潰與OutOfMemoryException

大部分內存保存在中,該內存填充在GeneratePurchaseReport的內部,一旦退出就不再使用 - 這就是爲什麼完整的GC收集將回收內存的原因。

我的問題是雙重的:

  1. 爲什麼不GC做到這一點對自己?一旦它在第二個GeneratePurchaseReport上的內存不足,它應該在崩潰和燃燒之前做一個完整的收集,不是嗎?
  2. 有沒有可以提高的內存限制?如果數據交換到磁盤,我一點都不介意,但.net進程使用的內存要少得多,甚至比可用的2.5GB內存少!我希望它只會在地址空間不足時崩潰,但在64位機器上我懷疑這種情況發生得太快。
+1

那麼,當我只看標題時,我認爲你需要某種壓力 - 應用程序來嘗試爲測試目的燒錄內存。 – 2011-05-19 04:51:57

+0

什麼是*** GeneratePurchaseReport ***? SSRS服務器中的本地報告RDLC或遠程RDL? – Kiquenet 2015-10-31 17:55:13

+0

** GcHelper ** https://github.com/mcctomsk/MccTomskHelpers/blob/560a079172468fd44ce952fbfcd676d297602442/Core/GcHelper.cs注意:http://stackoverflow.com/questions/10016541/garbage-collection-not-happening-even什麼時候需要 和_之所以GC.Collect被調用兩次:http://stackoverflow.com/questions/3829928/under-what-c​​ircumstances-we-need-to-call-gc-collect-twice_ – Kiquenet 2015-11-02 11:33:23

回答

3

我們需要查看您的代碼才能確定。

做不到這一點:

  • 你預上漿與項目的預期數量的清單?

  • 你可以預分配和使用數組而不是列表嗎? (裝箱/拆箱可能然後是一個額外的成本)

  • 即使在64位機器上,單個CLR對象可以是最大尺寸是2GB

  • 預分配一個MemoryStream容納整個報告,並寫信給那個。

有趣的是?:

我建議使用一個內存分析器如memprofiler,或展鵬(都有免費試用)查看問題實際存在的位置)。

5

閱讀上的大對象堆。

我認爲發生的事情是個別報告的最終文檔是隨着時間的推移而建立和追加的,這樣在每次追加操作中都會創建一個新文檔並丟棄舊文檔(可能發生在後臺)。該文檔(最終)大於大對象堆上存儲的85,000字節閾值。

在這種情況下,你實際上沒有使用太多的物理內存—它仍然可用於其他進程。你正在使用的是地址空間,可用於您的程序。 Windows中的每個進程都有自己的(通常)2GB地址空間。隨着時間的推移,當您分配不斷增長的報告文檔的新副本時,您會在收集先前副本時留下LOH中的許多漏洞。先前對象釋放的內存實際上不再使用,可用於其他進程,但地址空間仍然丟失;它是分散的,需要被壓縮。最終這個地址空間填滿了,你會得到一個OutOfMemory異常。

有證據表明調用GC.Collect()可以對LOH進行一些壓縮,但這不是一個完美的解決方案。幾乎所有我讀過的主題都表明GC.Collect()根本不應該壓縮LOH,但我已經在其中調用GC.Collect()時看到了幾個關於Stack Overflow的軼事報告。事實上能夠避免來自LOH碎片的OutOfMemory異常。

一個「更好的」解決方案(就確保您不會耗盡內存而言 - 使用GC.Collect()來壓縮LOH並不可靠)是將您的報告分解爲更小的單元超過85000字節,並將它們全部寫入到單個緩衝區中,或者使用一種數據結構,它不會隨着它的增長而丟掉之前的工作。不幸的是,這可能是更多的代碼。

這裏的一個相對簡單的選項是爲大於最大報告的MemoryStream對象分配一個緩衝區,然後在構建報告時寫入MemoryStream。這樣你就不會留下碎片。如果這只是寫入磁盤,你甚至可以直接進入FileStream(也許通過TextWriter,以便稍後更改)。這個選項可以解決你的問題,我想聽聽在評論這個答案。

+0

一切我已經讀過表明LOH從未被壓縮過?對象是垃圾收集在蕙(在第2代收集期間)(大概自由相鄰區域連接),但沒有壓縮,afaik。 – 2011-05-19 02:33:12

+1

http://msdn.microsoft.com/en-us/magazine/cc534993.aspx – 2011-05-19 02:34:16

+1

@Mitch - 它也沒有壓縮afaik,但我現在已經看到幾個實例,其中GC.Collect()能夠糾正明顯LOH碎片化,所以我開始懷疑更新或修補程序是否會使這些文章過時 – 2011-05-19 02:48:16

0

原因可能是大對象堆以及內部使用本地堆的任何對象,例如位圖類。大對象堆也是一個傳統的C堆,它的碎片。碎片化是這個問題的一個方面。

但我認爲它也與GC如何確定何時收集有關。它適用於正常的世代堆,但是對於其他堆中的已分配內存,特別是對於本地堆中的內存,它可能沒有足夠的信息來做出完美的決定。而LOH被視爲第二代,這意味着它收集的機會最小。

所以在你的情況下,我認爲手動強制收集是一個合理的解決方案。但是,這並不完美。 PS:我想給Joel的好解釋添加一些信息。對於普通對象,LOH的閾值爲85000字節,但對於雙數組,則爲8000字節。

-3

首先,垃圾回收運行於1個假設:堆的容量是無限的。垃圾收集器在內存不足時收集對象,但如果程序中不再使用任何對象,它將收集對象。取決於GC算法,我相信GC會將用於報告的內存標記爲仍在使用中。因此,它不能簡單地將其刪除。

當調用連續的GeneratePurchaseReport()時,GC沒有完成其工作的原因是因爲GC沒有一直運行。它採用某種算法來預測基於過去的行爲應該多少次收集垃圾。在你的情況下,它肯定不能預測垃圾需要在連續4行收集。

+3

這在幾點上是錯誤的。 – 2011-05-19 02:50:01

+1

如果堆的容量是無限的,GC將不是必需的。我想說GC的存在依賴於堆* *是有限的假設。 – cHao 2011-05-19 17:16:41