2011-10-14 48 views
11

我有一個Windows控制檯應用程序,應該在幾天或幾個月內不重新啓動的情況下運行。該應用程序從MSMQ中檢索「工作」並處理它。有30個線程同時處理工作塊。來自隊列的大對象堆和字符串對象

來自MSMQ的每個工作塊約爲200kb,其中大部分工作塊分配在一個String對象中。

我注意到,在處理了大約3-4,000個這樣的工作塊之後,應用程序的內存消耗非常高,消耗1 - 1.5 GB的內存。

我通過探查器運行應用程序,並注意到大部分這種內存(可能是一個演出等)沒有在大對象堆中使用,但結構是零散的。

我發現這些未使用(垃圾收集)字節中的90%是以前分配的字符串。然後我開始懷疑從MSMQ進入的字符串被分配,使用和解除分配,因此是碎片化的原因。

據我所知,像GC.Collect(2或GC.Max ...)這樣的東西不會幫助,因爲他們gc大對象堆但不壓縮它(這是這裏的問題)。所以我認爲我需要的是緩存這些字符串並以某種方式重新使用它們,但由於字符串是不可變的,我必須使用StringBuilders。

我的問題是:無論如何不改變底層結構(即使用MSMQ,因爲這是我不能改變的),並且仍然避免每次都初始化一個新的String以避免碎片化LOH?

感謝, 雅尼斯

UPDATE:關於這些 「作品」 塊目前如何檢索

目前,這些被存儲爲MSMQ WorkChunk對象。每個對象都包含一個名爲Contents的字符串和另一個名爲Headers的字符串。這些是實際的文本數據。如果需要,我可以將存儲結構更改爲其他的存儲結構,如果需要,可以將潛在的存儲機制更改爲MSMQ以外的其他存儲結構。

在工作節點側目前我們做

WorkChunk塊= _Queue.Receive();

所以在這個階段我們沒有什麼可以緩存的。如果我們以某種方式改變了結構,那麼我想我們可以取得一些進展。無論如何,我們必須解決這個問題,所以我們會盡一切努力避免拋出數月的工作。

更新:我繼續嘗試下面的一些建議,並注意到這個問題不能在我的本地機器上運行(運行Windows 7 x64和64位應用程序)。這使事情變得更加困難 - 如果有人知道爲什麼那麼它真的有助於在本地重新解決這個問題。

+0

你是如何收到這些字符串的?一旦他們是字符串,你就卡住了。我來自一個流或字節[]你可能有一些選擇。 –

+0

嗨亨克 - 看看更新以獲取有關這些工作塊的更多信息 – Yannis

+0

但這是一個實際的問題?具有> = 8GB RAM的64位PC上的1.5GB應該可以繼續。 –

回答

4

您的問題似乎是由於大對象堆上的內存分配 - 大對象堆未壓縮,因此可能是碎片來源。這裏有一個很好的一篇文章,進入更多細節,包括您可以按照確認大對象堆破碎化一些調試步驟正在發生的事情:

Large Object Heap Uncovered

你似乎有 2個 三種解決方案:

  1. 改變你的應用程序對每個塊小於85,000字節的塊/短字符串執行處理 - 這可以避免分配大對象。
  2. 改變您的應用程序以預先分配一些大塊內存,並通過將新消息複製到分配的內存中來重新使用這些塊。請參閱Heap fragmentation when using byte arrays
  3. 讓事情保持原樣 - 只要您不會遇到內存不足異常,並且應用程序不會干擾系統上運行的其他應用程序,您應該保持原樣。

這裏重要的是要理解虛擬內存和物理內存之間的區別 - 即使進程使用大量的虛擬內存,如果分配的對象數量相對較少,那麼物理內存該進程的使用率很低(未使用的內存被分頁到磁盤),這意味着對系統上的其他進程的影響很小。您也可能會發現「虛擬機囤積」選項有助於 - 閱讀「大型對象堆未發現」文章以獲取更多信息。

這兩個變化都涉及到改變你的應用程序來使用字節數組和短子串而不是單個大字符串來執行它的一些或全部處理 - 這對你來說有多困難取決於它是什麼類型的處理你在做什麼。

+0

謝謝賈斯汀。問題是這些字符串通過消息隊列來自不同的系統。所以我現在不能說「獲得一半的工作塊」,除非我改變整體存儲結構 - 我想這就是我需要的想法和建議 – Yannis

+0

@Yannis如果你想改變你的應用程序,那麼它看起來就是這樣 - 對於建議關於你如何做這件事,可能需要更多關於正在完成的處理的細節。你見過我最新的編輯嗎?你應該認爲你看到的這種行爲可能是完全正確的(只要你沒有得到OOM例外,這是一個32位還是64位的進程?) – Justin

+0

Justin - 這是一個64位的進程,結果是計算機(Windows 2008 Server)由於分頁過多而變慢。這是有道理的。讓我問一下:如果將字符串內容屬性更改爲char [] [],其中包含85k的char數據塊的字符數組(這是將事情放在LOH上的限制) - 會有幫助嗎? – Yannis

1

也許您可以創建一個字符串對象池,您可以在處理該工作時使用該對象池,然後在完成後返回。

一旦在LOH中創建了一個大對象,它就不能被刪除(AFAIK),所以如果你不能避免創建這些對象,那麼最好的方案是重用它們。

如果您可以在兩端更改協議,那麼將您的'Contents'字符串縮減爲一組較小的字符串(各爲<)應該阻止它們存儲在LOH中。

+0

這就是OP已經說過的。但是,如何重用字符串? –

+0

對原始文章添加了編輯信息 – Yannis

+0

Tony - 問題在於序列化這些內容並在另一端反序列化它們。無論我做什麼,這個對象都會以這種或那種方式包含這些「內容」 - 即使是在小塊中。 – Yannis

2

當LOH出現碎片時,表示有分配的對象。如果您能延緩延遲,您可以稍等一會,直到所有當前正在運行的任務都完成並致電GC.Collect()。當沒有引用大型對象時,它們都將被收集起來,有效地消除了LOH的碎片。當然,這隻適用於(全部)所有大對象都未被引用的情況。

另外,移動到64位操作系統也可能有所幫助,因爲由於碎片導致的內存不足很可能成爲64位系統的問題,因爲虛擬空間幾乎是無限的。

+0

Steven我認爲你錯了,因爲碎片並不意味着對象在那裏(在LOH中),但他們曾經在那裏,並最終被解除分配從而在蕙蘭留下了一塊空白的大塊。這意味着如果有一個120k的塊(比如說),並且我們試圖分配121k,那麼這將在第一個可用的121k字節連續塊中分配,從而使120k塊空着。 不幸的是,GC.Collect()只會取消分配LOH對象(並且需要GC.Collect(GC.MaxGeneration))並且不會壓縮LOH。 – Yannis

+1

我不認爲史蒂文說GC.Collect會緊湊,我想他是說當你只有幾件東西在旅途中叫它。這樣它就會擺脫它們之間的巨大對象,並留下一個不錯的(ish)乾淨的石板。 – Joey

+1

@Yannis:我的意思是:一個空的LOH不能被分割。喬伊很好地重述了它。 – Steven

0

如何使用String.Intern(...)來消除重複引用。它有一個性能損失,但取決於你的字符串,它可能會產生影響。

+0

如果您可以將您的標題和內容切分爲鍵/值對,並在所有鍵和值上執行.Intern,它會更好。然後你將不會有重複的數據,而是一個不同的數據結構,這可能需要更多的處理。 –