2012-06-21 114 views
0

我正在使用mstor庫來分析mbox郵件文件。某些文件的大小超過了千兆字節。如你所想,這可能會導致一些堆空間問題。Java - 避免重複手動垃圾回收 - mstor和javaxmail OutOfMemoryError

有一個循環,對於每個迭代,檢索一個特定的消息。 getMessage()調用是當它耗盡時試圖分配堆空間。如果我在此循環的頂部添加一個System.gc()的調用,程序將毫無錯誤地解析大文件,但我意識到收集垃圾40,000次必須減慢程序的運行速度。

我的第一次嘗試是使電話看起來像if (i % 500 == 0) System.gc(),使呼叫每500個記錄發生。我試着提高並降低這個數字,但結果不一致,通常會返回一個OutOfMemory錯誤。

我的第二個,更聰明的嘗試看起來是這樣的:

try { 
    message = inbox.getMessage(i); 
} catch (OutOfMemoryError e) { 
    if (firstTry) { 
     i--; 
     firstTry = false; 
    } else { 
     firstTry = true; 
     System.out.println("Message " + i + " skipped."); 
    } 
    System.gc(); 
    continue; 
} 

的想法是隻調用垃圾收集器如果內存不足的錯誤被拋出,然後再遞減計數再試一次。不幸的是,解析幾千封電子郵件後,該計劃只是開始輸出:

Message 7030 skipped. 
Message 7031 skipped. 
.... 

等對他們的休息。

我只是困惑,如何按每個迭代收集器將返回不同的結果比這個。根據我的理解,垃圾是垃圾,所有這些應該改變的是在特定時間收集多少垃圾。

任何人都可以解釋這種奇怪的行爲?有沒有人有其他方式的建議,以更少的頻率調用收集器?我的堆空間最多了。

+3

你試過增加堆空間嗎? –

+0

@VivinPaliath他的堆空間最大是問題的最後一句...... – fvu

+1

@fvu您可以使用'-Xmx'來增加堆空間。除非他的意思是他的機器沒有足夠的內存來做這件事。 –

回答

0

mstor庫沒有很好地處理消息的緩存。在做了一些研究之後,我發現如果您撥打Folder.close()(收件箱是我上面的文件夾對象),則mstor和javaxmail將釋放由於getMessage()方法而緩存的所有郵件。

我做了try/catch塊是這樣的:

try { 
    message = inbox.getMessage(i); 
    // moved all of my calls to message.getFrom(), 
    // message.getAllRecipients(), etc. inside this try/catch. 
} catch (OutOfMemoryError e) { 
    if (firstTry) { 
     i--; 
     firstTry = false; 
    } else { 
     firstTry = true; 
     System.out.println("Message " + i + " skipped."); 
    } 
    inbox.close(false); 
    System.gc(); 
    inbox.open(Folder.READ_ONLY); 
    continue; 
} 
firstTry = true; 

每次catch語句被擊中,它需要40-50毫秒手動清除緩存的消息,並重新打開該文件夾。

通過每次迭代調用垃圾回收器,它需要57分鐘來解析1.6千兆字節的文件。有了這個邏輯,解析同一個文件只需要18分鐘。

更新 - 降低mstor使用的內存量的另一個重要方面是緩存屬性。其他人已經提到將「mstor.cache.disabled」設置爲true,這有所幫助。今天,我發現了另一個重要屬性,它可以大大減少更大文件的OOM捕獲量。

Properties props = new Properties(); 
    props.setProperty("mstor.mbox.metadataStrategy", "none"); 
    props.setProperty("mstor.cache.disabled", "true"); 
    props.setProperty("mstor.mbox.cacheBuffers", "false"); // most important 
+1

嗯,你不應該捕捉'OutOfMemoryError'並且明確地調用'System.gc()'。那些代碼味道很差。 mstor應該有一種方法來控制緩存行爲。 –

+0

我調整了mstor緩存屬性無濟於事。我可以很容易地打開和關閉每500或1000次迭代文件夾,但我想這會比這慢。我認爲這是規則的一個很好的例外。如果沒有例外,我們將無法訪問垃圾收集器等內容。此方法可確保我只根據需要執行資源密集型打開和關閉Folder對象的操作。對於較小的文件,它甚至不會被調用。思考? –

+0

這似乎很奇怪。我想知道這是否是API的限制。我想你總是可以發送電子郵件給他們,並問他們處理這種情況的最好方法。 –

1

您不應該依賴System.gc(),因爲它可以被VM忽略。如果您使用OutOfMemory,則表示VM已嘗試運行GC。你可以嘗試增加堆的大小,改變堆的世代大小(比如說大多數對象最終會在老一代中出現,那麼你不需要太多年輕一代的內存),請檢查你的代碼以確保你沒有持有任何引用到您不需要的資源。

1

調用System.gc()是在一般意義上浪費時間,但並不能保證在任何時候做任何事情,它是一個建議充其量,在大多數情況下被忽略。在OutOfMemoryException之後調用它更無用,因爲在拋出異常之前,JVM已嘗試回收內存。

如果您使用的是無法控制的第三方代碼,則唯一可以做的事情是在命令行中將JVM堆分配增加到最大程度,以便您的特定計算機可以處理。

Get started with java JVM memory (heap, stack, -xss -xms -xmx -xmn...)

1

這裏是我的建議:

  • 增加堆空間。這可能是最容易做的事情。你可以用-Xmx來做到這一點。參數。
  • 查看加載消息的API是否提供「流式傳輸」選項。也許你不需要把整個消息一次加載到內存中。

調用System.gc()對您不會有任何好處,因爲它不能保證調用GC。實際上,這是一個錯誤代碼的明確標誌。如果您要依靠System.gc()來使代碼正常工作,那麼您的代碼可能已損壞。在這種情況下,你似乎是爲了性能而依賴它,這是一個跡象表明你的代碼是肯定損壞。

您永遠無法確定JVM是否會遵守您的請求,而且您也無法知道它將如何執行垃圾回收。 JVM可能會決定完全忽略您的請求(即,它不是保證)。是否System.gc()會做它應該做的事情,是相當可笑的。由於其行爲不能得到保證,因此最好不要完全使用它。

最後,您可以使用-XX:DisableExplicitGC選項,這意味着再次禁用顯式調用System.gc(),它不能保證你的System.gc()的通話將運行,因爲它可能會在已配置爲忽略一個JVM上運行明確的呼叫。

+1

你的帖子是最激勵人心的,謝謝你指出我在正確的方向!查看我的答案以獲得最終解決方案。 –

1

默認情況下,mstor將緩存從ehcache緩存中的文件夾中檢索到的消息,以加快訪問速度。但是,此緩存可能會被禁用,我建議禁用它爲大文件夾。

您可以通過使用以下內容您的classpath的根目錄創建一個名爲「mstor.properties」的文本文件禁用緩存:

mstor.cache.disabled=true 

您還可以設置這個值作爲一個系統屬性:

java -Dmstor.cache.disabled=true SomeProgram