2012-03-03 108 views
14

我有一個運行在我的Ubuntu 10.04機器上的Java程序,並且在沒有任何用戶交互的情況下反覆查詢MySQL數據庫,然後根據從數據庫中讀取的數據構造img和txt文件D B。它使數以萬計的查詢和創建數以萬計的文件。Java程序的內存消耗問題

經過幾個小時的運行後,我的機器上的可用內存(包括交換空間)完全用完。我還沒有開始其他程序,並且在後臺運行的進程不會消耗太多內存,並且不會真正增加消耗。

爲了找出分配這麼多內存的內容,我想分析一個堆轉儲,所以我使用-Xms64m -Xmx128m -XX:+ HeapDumpOnOutOfMemoryError啓動了該進程。

令我驚訝的是,情況和之前一樣,幾個小時後程序分配了所有交換,超過了給定的最大值128m。

用VisualVM調試的另一個運行表明,堆的分配永遠不會超過128M的最大值 - 當分配內存接近最大值時,它的很大一部分會再次釋放(我假設垃圾回收器)。

因此,它不會成爲一個穩步增長的問題堆。

當存儲器被全部用完:

免費顯示以下內容:

   total  used  free  shared buffers  cached 
Mem:  2060180 2004860  55320   0  848 1042908 
-/+ buffers/cache:  961104 1099076 
Swap:  3227640 3227640   0 

頂部示出了以下內容:

USER VIRT RES  SHR  COMMAND 
[my_id] 504m 171m 4520 java 
[my_id] 371m 162m 4368 java 

(迄今爲止兩個 「最大」 的過程和唯一的java進程運行)

我的第一個問題是:

  • 我該如何找到操作系統級別(例如,與命令行工具)什麼是分配這麼多的內存? top/htop沒有幫助我。在很多情況下,許多相同類型的微小進程會消耗內存:是否有智能地總結類似進程的方法? (我知道這可能是題外話,因爲它是一個Linux/Ubuntu的問題,但我的主要問題仍可能與Java相關的)

我的老問題是:

  • 爲什麼不是在頂部輸出中給出的程序的內存消耗?
  • 如何找出分配如此之多的內存?
  • 如果堆不是問題,是堆棧中唯一的「分配因子」? ( 堆棧不應該是一個問題,因爲沒有深度的「方法調用深度」)
  • 作爲數據庫連接的外部資源如何?
+0

嘗試使用性能分析工具:http://stackoverflow.com/a/9205812/90909 – qrtt1 2012-03-03 13:31:32

+0

@ qrtt1:我使用了VisualVM,但是這表明堆不是問題(參見上文)。 – 2012-03-03 13:40:42

+1

您可以在這裏找到答案,我認爲http://stackoverflow.com/a/9306054/1140748和http://www.oracle.com/technetwork/java/hotspotfaq-138619.html#gc_oom(請參閱錯誤說明) – 2012-03-03 13:43:01

回答

0

因爲一天之後我沒問題(直到3月23日),而且我仍然找不到內存消耗的原因,所以我「務實」地解決了這個問題。

該程序引起問題基本上是一個「任務」的重複(即查詢DB,然後創建文件)。對程序進行參數化是相對容易的,以便執行某個子集的任務,而不是全部。

所以現在我反覆從shell腳本運行我的程序,每個進程只執行一組任務(通過參數進行參數化)。最後,所有任務都正在執行,但由於單個進程只處理一部分任務,因此不再有內存問題。

對我來說這是一個足夠的解決方案。如果你有類似的問題,你的程序有一個類似批處理的執行結構,這可能是一個實用的方法。

當我找到時間後,我會看看新的建議,希望找出根本原因(感謝您的幫助!)。

1

嗯...使用ipcs檢查共享內存段是否保持打開狀態。檢查JVM的打開文件描述符(/proc/<jvm proccess id>/fd/*)。在頂部,鍵入fpFp來顯示交換和按照交換任務列表進行排序。

這就是我現在所能想到的,希望它至少有一點幫助。

0

你的文件系統緩存可能是導致此,文件系統緩存會做大量的IO的時候吃了所有可用內存。您的系統性能不應受此行爲的負面影響,內核將在進程請求內存時立即釋放文件系統緩存。

+0

但是,內核不會交換正在運行的進程,直到緩存完全釋放... – 2012-03-27 22:18:53

+0

我不認爲這可能是我所看到的;發生這種情況時,我的系統確實受到了非常不利的影響它會消耗內存,直到使用所有可用的RAM,然後它將開始使用交換。正如你可以想象的那樣,在這一點上,事情很快就會停頓下來! – Jan 2012-03-28 10:37:36

7

如果您的Java進程確實需要內存,並且VisualVM或內存轉儲中沒有suspucios,那麼它必須位於本機代碼的某處 - 無論是在JVM還是在您正在使用的某些庫中。例如,在JVM級別上,可能會使用NIO或內存映射文件。如果你的一些庫正在使用本地調用,或者你的數據庫沒有使用類型4的JDBC驅動程序,那麼泄漏可能在那裏。

幾點建議:

  • 還有一些細節如何找到本地代碼here內存泄漏。好read也。
  • 像往常一樣,請確保您正確關閉所有資源(文件,流,連接,線程等)。大多數的這些呼籲在一些點本地實現,因此消耗的內存在JVM可能不是直接可見的消耗在操作系統級別
  • 檢查資源 - 打開文件,文件描述符,網絡連接等數
+0

感謝您的回覆。本機代碼泄漏似乎是一種可能性。我們不使用任何我知道的本地庫,甚至不使用JDBC。我們當然會儘量小心關閉所有文件等,但是我確實很難理解如何讓這些資源打開可能會導致一個已被賦予16 GB堆的JVM消耗超過24 GB。當然,在文件句柄佔用大量內存之前,操作系統會限制打開文件的數量? – Jan 2012-03-28 10:41:14

+0

如果不知道程序的內部部件,很難提供更好的建議。根據你爲JVM分配的內存來判斷,你必須做某種緩存或者從某處加載大量數據等等。另外,在原來的問題中,分配的內存量肯定不是16Gb,所以我不確定你是否指向相同或不相同的情況。 – maximdim 2012-03-28 12:00:54

2

@ maximdim的對於這種情況,答案是很好的一般建議。這裏可能發生的事情是,一個非常小的Java對象被保留下來,導致一些更大量的本機(OS級)內存掛起。這個本地內存不在Java堆中考慮。 Java對象可能非常小,以至於在Java對象保留將壓倒堆之前,您將達到系統內存限制。

所以找到這個訣竅是使用連續的堆轉儲,足夠遠,你已經注意到了,整個過程內存增長,但沒有多大的分歧,一個工作噸已經上。您正在尋找的是堆中的Java對象數量不斷增加並附加了本地內存。

這些可能是文件句柄,插座,數據庫連接,或圖像處理有可能直接適用於你只是僅舉幾例。

在更罕見的情況下,有一個由java實現泄露,即使在Java對象是垃圾回收原生資源。我曾經遇到過一個WinCE 5的bug,每個socket都關閉了4k。所以沒有Java對象的增長,但有進程內存使用增長。在這些情況下,製作一些計數器並跟蹤本機內存中對象的java分配與實際增長之間的關係會很有幫助。然後在一個足夠短的窗口中,您可以查找任何相關性,並使用它們製作較小的測試用例。其他

一個提示,確保您的所有關閉操作是在finally塊,以防萬一有異常彈出你出你的正常流動。這已被認爲會導致這類問題。

+0

嗨,詹姆斯,感謝您的評論,這很有趣。但正如我在@maximdim的回覆中所說的,我們看到進程消耗的內存量穩定增加,比分配的堆量多出幾千兆字節。由於其他原因,我們一直在認真追查尚未關閉的文件等,但是我看不到如何打開文件或套接字可能導致千兆字節的內存泄漏!此外,有問題的過程是一個服務器端的過程,所以不使用圖形庫,加載圖像等... – Jan 2012-03-28 10:45:39

+0

如果您處理大量請求,4K的句柄可以快速加起來。還要確保你在finally塊中關閉了所有的ResultSet實例。根據您的JDBC連接器版本,泄漏的ResultSet對象可能導致本機內存保留。 – 2012-03-28 12:18:10

+0

公平點,儘管它需要2,000,000泄漏文件句柄在4k一塊泄漏8Gb,並且操作系統的限制比這個設置要低得多。另外,我們在這個過程中不使用JDBC。 – Jan 2012-03-28 13:37:32

1

正如@maximdim和@JamesBranigan指出的,可能的罪魁禍首是您的代碼中的一些本地交互。但是,由於您無法準確追蹤有問題的互動使用可用工具的位置,爲什麼不嘗試暴力方法呢?

您已經概述了兩部分流程:查詢MySQL並寫入文件。這些事情中的任何一個都可以作爲測試從流程中排除。測試一:消除查詢並硬編碼已經返回的內容。測試二:進行查詢,但不要打擾寫文件。你還有漏洞嗎?

也可能有其他可測試的情況,具體取決於您的應用程序做了什麼。

1

您是否創建單獨的線程來運行您的「任務」?用於創建線程的內存與Java堆分開。

這意味着即使您指定了-Xmx128m,Java進程使用的內存也可能更高,具體取決於您使用的線程數和線程堆棧大小(每個線程獲得一個分配的堆棧,其大小由-Xss)。

從最近的工作實例: 我們有4GB(-Xmx4G)的Java堆,但操作系統過程耗時向上6GB的, 也利用了交換空間。 當我用cat /proc/<PID>/status檢查進程狀態時,我注意到我們有11000個線程在運行。 由於我們設置了-Xss256K,因此很容易解釋:10000個線程表示2.5GB。

0

你說你正在創建圖像文件是你創建圖像對象嗎?如果是這樣,你完成後是否要調用dispose()這些對象?

如果我沒有記錯,java awt想象對象會分配必須明確處理的本地資源。