防范JAVA內存泄漏解決方案
編者按:Java內存泄漏是每個Java程序員都會遇到的問題,程序在本地運行一切正常,可是布署到遠端就會出現(xiàn)內存無限制的增長,最后系統(tǒng)癱瘓,那么如何最快最好的檢測程序的穩(wěn)定性,防止系統(tǒng)崩盤,作者用自已的親身經(jīng)歷與各位網(wǎng)友分享解決這些問題的辦法。 作為Internet最流行的編程語言之一,Java現(xiàn)正非常流行。我們的網(wǎng)絡應用程序就主要采用Java語言開發(fā),大體上分為客戶端、服務器和數(shù)據(jù)庫三個層次。在進入測試過程中,我們發(fā)現(xiàn)有一個程序模塊系統(tǒng)內存和CPU資源消耗急劇增加,持續(xù)增長到出現(xiàn) java.lang.OutOfMemoryError為止。經(jīng)過分析Java內存泄漏是破壞系統(tǒng)的主要因素。這里與大家分享我們在開發(fā)過程中遇到的 Java內存泄漏的檢測和處理解決過程. 一. Java是如何管理內存 為了判斷Java中是否有內存泄露,我們首先必須了解Java是如何管理內存的。Java的內存管理就是對象的分配和釋放問題。在Java中,內存的分配是由程序完成的,而內存的釋放是由垃圾收集器(Garbage Collection,GC)完成的,程序員不需要通過調用函數(shù)來釋放內存,但它只能回收無用并且不再被其它對象引用的那些對象所占用的空間。 Java的內存垃圾回收機制是從程序的主要運行對象開始檢查引用鏈,當遍歷一遍后發(fā)現(xiàn)沒有被引用的孤立對象就作為垃圾回收。GC為了能夠正確釋放對象,必須監(jiān)控每一個對象的運行狀態(tài),包括對象的申請、引用、被引用、賦值等,GC都需要進行監(jiān)控。監(jiān)視對象狀態(tài)是為了更加準確地、及時地釋放對象,而釋放對象的根本原則就是該對象不再被引用。 在Java中,這些無用的對象都由GC負責回收,因此程序員不需要考慮這部分的內存泄露。雖然,我們有幾個函數(shù)可以訪問GC,例如運行GC的函數(shù) System.gc(),但是根據(jù)Java語言規(guī)范定義,該函數(shù)不保證JVM的垃圾收集器一定會執(zhí)行。因為不同的JVM實現(xiàn)者可能使用不同的算法管理 GC。通常GC的線程的優(yōu)先級別較低。JVM調用GC的策略也有很多種,有的是內存使用到達一定程度時,GC才開始工作,也有定時執(zhí)行的,有的是平緩執(zhí)行 GC,有的是中斷式執(zhí)行GC。但通常來說,我們不需要關心這些。 1
實際上這些對象已經(jīng)是無用的,但還被引用,GC就無能為力了(事實上GC認為它還有用),這一點是導致內存泄漏最重要的原因。再引用另一個例子來說明Java的內存泄漏。假設有一個日志類Logger,其提供一個靜態(tài)的log(String msg),任何其它類都可以調用Logger.Log(message)來將message的內容記錄到系統(tǒng)的日志文件中。 1
1
1
4.2處理內存泄漏的方法 一旦知道確實發(fā)生了內存泄漏,就需要更專業(yè)的工具來查明為什么會發(fā)生泄漏。JVM自己是不會告訴您的。這些專業(yè)工具從JVM獲得內存系統(tǒng)信息的方法基本上有兩種:JVMTI和字節(jié)碼技術(byte code instrumentation)。Java虛擬機工具接口(Java Virtual Machine Tools Interface,JVMTI)及其前身Java虛擬機監(jiān)視程序接口(Java Virtual Machine Profiling Interface,JVMPI)是外部工具與JVM通信并從JVM收集信息的標準化接口。字節(jié)碼技術是指使用探測器處理字節(jié)碼以獲得工具所需的信息的技術。 Optimizeit是Borland公司的產品,主要用于協(xié)助對軟件系統(tǒng)進行代碼優(yōu)化和故障診斷,其中的Optimizeit Profiler主要用于內存泄漏的分析。Profiler的堆視圖就是用來觀察系統(tǒng)運行使用的內存大小和各個類的實例分配的個數(shù)的。 首先,Profiler會進行趨勢分析,找出是哪個類的對象在泄漏。系統(tǒng)運行長時間后可以得到四個內存快照。對這四個內存快照進行綜合分析,如果每一次快照的內存使用都比上一次有增長,可以認定系統(tǒng)存在內存泄漏,找出在四個快照中實例個數(shù)都保持增長的類,這些類可以初步被認定為存在泄漏。通過數(shù)據(jù)收集和初步分析,可以得出初步結論:系統(tǒng)是否存在內存泄漏和哪些對象存在泄漏(被泄漏)。 接下來,看看有哪些其他的類與泄漏的類的對象相關聯(lián)。前面已經(jīng)談到Java中的內存泄漏就是無用的對象保持,簡單地說就是因為編碼的錯誤導致了一條本來不應該存在的引用鏈的存在(從而導致了被引用的對象無法釋放),因此內存泄漏分析的任務就是找出這條多余的引用鏈,并找到其形成的原因。查看對象分配到哪里是很有用的。同時只知道它們如何與其他對象相關聯(lián)(即哪些對象引用了它們)是不夠的,關于它們在何處創(chuàng)建的信息也很有用。 最后,進一步研究單個對象,看看它們是如何互相關聯(lián)的。借助于Profiler工具,應用程序中的代碼可以在分配時進行動態(tài)添加,以創(chuàng)建堆棧跟蹤。也有可以對系統(tǒng)中所有對象分配進行動態(tài)的堆棧跟蹤。這些堆棧跟蹤可以在工具中進行累積和分析。對每個被泄漏的實例對象,必然存在一條從某個牽引對象出發(fā)到達該對象的引用鏈。處于堆棧空間的牽引對象在被從棧中彈出后就失去其牽引的能力,變?yōu)榉菭恳龑ο蟆R虼耍陂L時間的運行后,被泄露的對象基本上都是被作為類的靜態(tài)變量的牽引對象牽引。 總而言之, Java雖然有自動回收管理內存的功能,但內存泄漏也是不容忽視,它往往是破壞系統(tǒng)穩(wěn)定性的重要因素。 |