java 程式記憶體溢位

dvlue發表於2009-04-17

記憶體溢位與資料庫鎖表的問題,可以說是開發人員的噩夢,一般的程式異常,總是可以知道在什麼時候或是在什麼操作步驟上出現了異常,而且根據堆疊資訊也很容易定位到程式中是某處出現了問題。記憶體溢位與鎖表則不然,一般現象是操作一般時間後系統越來越慢,直到當機,但並不能明確是在什麼操作上出現的,發生的時間點也沒有規律,檢視日誌或檢視資料庫也不能定位出問題的程式碼。

更嚴重的是記憶體溢位與資料庫鎖表在系統開發和單元測試階段並不容易被發現,當系統正式上線一般時間後,操作的併發量上來了,資料也積累了一些,系統就容易出現記憶體溢位或是鎖表的現象,而此時系統又不能隨意停機或重啟,為修正BUG帶來很大的困難。

本文以筆者開發和支援的多個專案為例,與大家分享在開發過程中遇到的Java記憶體溢位和資料庫鎖表的檢測和處理解決過程。

2.記憶體溢位的分析

記憶體溢位是指應用系統中存在無法回收的記憶體或使用的記憶體過多,最終使得程式執行要用到的記憶體大於虛擬機器能提供的最大記憶體。為了解決Java中記憶體溢位問題,我們首先必須瞭解Java是如何管理記憶體的。Java的記憶體管理就是物件的分配和釋放問題。在Java中,記憶體的分配是由程式完成的,而記憶體的釋放是由垃圾收集器(Garbage CollectionGC)完成的,程式設計師不需要通過呼叫GC函式來釋放記憶體,因為不同的JVM實現者可能使用不同的演算法管理GC,有的是記憶體使用到達一定程度時,GC才開始工作,也有定時執行的,有的是中斷式執行GC。但GC只能回收無用並且不再被其它物件引用的那些物件所佔用的空間。 Java的記憶體垃圾回收機制是從程式的主要執行物件開始檢查引用鏈,當遍歷一遍後發現沒有被引用的孤立物件就作為垃圾回收。

引起記憶體溢位的原因有很多種,常見的有以下幾種:

l          記憶體中載入的資料量過於龐大,如一次從資料庫取出過多資料;

l          集合類中有對物件的引用,使用完後未清空,使得JVM不能回收;

l          程式碼中存在死迴圈或迴圈產生過多重複的物件實體;

l          使用的第三方軟體中的BUG

l          啟動引數記憶體值設定的過小;

3.記憶體溢位的解決

記憶體溢位雖然很棘手,但也有相應的解決辦法,可以按照從易到難,一步步的解決。

第一步,就是修改JVM啟動引數,直接增加記憶體。這一點看上去似乎很簡單,但很容易被忽略。JVM預設可以使用的記憶體為64MTomcat預設可以使用的記憶體為128MB,對於稍複雜一點的系統就會不夠用。在某專案中,就因為啟動引數使用的預設值,經常報“OutOfMemory”錯誤。因此,-Xms-Xmx引數一定不要忘記加。

第二步,檢查錯誤日誌,檢視“OutOfMemory”錯誤前是否有其它異常或錯誤。在一個專案中,使用兩個資料庫連線,其中專用於傳送簡訊的資料庫連線使用DBCP連線池管理,使用者為不將簡訊發出,有意將資料庫連線使用者名稱改錯,使得日誌中有許多資料庫連線異常的日誌,一段時間後,就出現“OutOfMemory”錯誤。經分析,這是由於DBCP連線池BUG引起的,資料庫連線不上後,沒有將連線釋放,最終使得DBCP報“OutOfMemory”錯誤。經過修改正確資料庫連線引數後,就沒有再出現記憶體溢位的錯誤。

檢視日誌對於分析記憶體溢位是非常重要的,通過仔細檢視日誌,分析記憶體溢位前做過哪些操作,可以大致定位有問題的模組。

第三步,安排有經驗的程式設計人員對程式碼進行走查和分析,找出可能發生記憶體溢位的位置。重點排查以下幾點:

l          檢查程式碼中是否有死迴圈或遞迴呼叫。

l          檢查是否有大迴圈重複產生新物件實體。

l          檢查對資料庫查詢中,是否有一次獲得全部資料的查詢。一般來說,如果一次取十萬條記錄到記憶體,就可能引起記憶體溢位。這個問題比較隱蔽,在上線前,資料庫中資料較少,不容易出問題,上線後,資料庫中資料多了,一次查詢就有可能引起記憶體溢位。因此對於資料庫查詢儘量採用分頁的方式查詢。

l          檢查ListMAP等集合物件是否有使用完後,未清除的問題。ListMAP等集合物件會始終存有對物件的引用,使得這些物件不能被GC回收。

第四步,使用記憶體檢視工具動態檢視記憶體使用情況。某個專案上線後,每次系統啟動兩天後,就會出現記憶體溢位的錯誤。這種情況一般是程式碼中出現了緩慢的記憶體洩漏,用上面三個步驟解決不了,這就需要使用記憶體檢視工具了。

記憶體檢視工具有許多,比較有名的有:Optimizeit ProfilerJProbe ProfilerJinSightJava1.5Jconsole等。它們的基本工作原理大同小異,都是監測Java程式執行時所有物件的申請、釋放等動作,將記憶體管理的所有資訊進行統計、分析、視覺化。開發人員可以根據這些資訊判斷程式是否有記憶體洩漏問題。一般來說,一個正常的系統在其啟動完成後其記憶體的佔用量是基本穩定的,而不應該是無限制的增長的。持續地觀察系統執行時使用的記憶體的大小,可以看到在記憶體使用監控視窗中是基本規則的鋸齒形的圖線,如果記憶體的大小持續地增長,則說明系統存在記憶體洩漏問題。通過間隔一段時間取一次記憶體快照,然後對記憶體快照中物件的使用與引用等資訊進行比對與分析,可以找出是哪個類的物件在洩漏。

通過以上四個步驟的分析與處理,基本能處理記憶體溢位的問題。當然,在這些過程中也需要相當的經驗與敏感度,需要在實際的開發與除錯過程中不斷積累。

 

總體上來說,產生記憶體溢位是由於程式碼寫的不好造成的,因此提高程式碼的質量是最根本的解決辦法。有的人認為先把功能實現,有BUG時再在測試階段進行修正,這種想法是錯誤的。正如一件產品的質量是在生產製造的過程中決定的,而不是質量檢測時決定的,軟體的質量在設計與編碼階段就已經決定了,測試只是對軟體質量的一個驗證,因為測試不可能找出軟體中所有的BUG

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/7656893/viewspace-590745/,如需轉載,請註明出處,否則將追究法律責任。

相關文章