整理java幾種常見記憶體泄露及處理方法

來源:互聯網
上載者:User

如果你對java記憶體流失的原理還不是很清楚,你先看看這篇文章:Java記憶體流失原理及如何防止。


一. Java是如何管理記憶體

為了判斷Java中是否有記憶體泄露,我們首先必須瞭解Java是如何管理記憶體的。Java的記憶體管理就是對象的分配和釋放問題。在Java中,記憶體的分配是由程式完成的,而記憶體的釋放是由垃圾收集器(Garbage Collection,GC)完成的,程式員不需要通過調用函數來釋放記憶體,但它只能回收無用並且不再被其它對象引用的那些對象所佔用的空間。

Java的記憶體記憶體回收機制是從程式的主要運行對象開始檢查引用鏈,當遍曆一遍後發現沒有被引用的孤立對象就作為記憶體回收。GC為了能夠正確釋放對象,必須監控每一個對象的運行狀態,包括對象的申請、引用、被引用、賦值等,GC都需要進行監控。監視對象狀態是為了更加準確地、及時地釋放對象,而釋放對象的根本原則就是該對象不再被引用。

在Java中,這些無用的對象都由GC負責回收,因此程式員不需要考慮這部分的記憶體泄露。雖然,我們有幾個函數可以訪問GC,例如運行GC的函數System.gc(),但是根據Java語言規範定義,該函數不保證JVM的垃圾收集器一定會執行。因為不同的JVM實現者可能使用不同的演算法管理GC。通常GC的線程的優先順序別較低。JVM調用GC的策略也有很多種,有的是記憶體使用量到達一定程度時,GC才開始工作,也有定時執行的,有的是平緩執行GC,有的是中斷式執行GC。但通常來說,我們不需要關心這些。

二. 什麼是Java中的記憶體泄露

導致記憶體流失主要的原因是,先前申請了記憶體空間而忘記了釋放。如果程式中存在對無用對象的引用,那麼這些對象就會駐留記憶體,消耗記憶體,因為無法讓記憶體回收行程GC驗證這些對象是否不再需要。如果存在對象的引用,這個對象就被定義為"有效活動",同時不會被釋放。要確定對象所佔記憶體將被回收,我們就要務必確認該對象不再會被使用。典型的做法就是把對象資料成員設為null或者從集合中移除該對象。但當局部變數不需要時,不需明顯的設為null,因為一個方法執行完畢時,這些引用會自動被清理。

在Java中,記憶體流失就是存在一些被分配的對象,這些對象有下面兩個特點,首先,這些對象是有被引用的,即在有向樹形圖中,存在樹枝通路可以與其相連;其次,這些對象是無用的,即程式以後不會再使用這些對象。如果對象滿足這兩個條件,這些對象就可以判定為Java中的記憶體流失,這些對象不會被GC所回收,然而它卻佔用記憶體。

這裡引用一個常看到的例子,在下面的代碼中,迴圈申請Object對象,並將所申請的對象放入一個Vector中,如果僅僅釋放對象本身,但因為Vector仍然引用該對象,所以這個對象對GC來說是不可回收的。因此,如果對象加入到Vector後,還必須從Vector中刪除,最簡單的方法就是將Vector對象設定為null。

Vector v = new Vector(10);

for (int i = 1; i < 100; i++)

......{

 Object o = new Object();

 v.add(o);

 o = null;

}//此時,所有的Object對象都沒有被釋放,因為變數v引用這些對象。



實際上這些對象已經是無用的,但還被引用,GC就無能為力了(事實上GC認為它還有用),這一點是導致記憶體流失最重要的原因。 再引用另一個例子來說明Java的記憶體流失。假設有一個日誌類Logger,其提供一個靜態log(String msg),任何其它類都可以調用Logger.Log(message)來將message的內容記錄到系統的記錄檔中。

Logger類有一個類型為HashMap的靜態變數temp,每次在執行log(message)的時候,都首先將message的值寫入temp中(以當前線程+目前時間為鍵),在退出之前再從temp中將以當前線程和目前時間為鍵的條目刪除。注意,這裡目前時間是不斷變化的,所以log在退出之前執行刪除條目的操作並不能刪除執行之初寫入的條目。這樣,任何一個作為參數傳給log的字串最終由於被Logger的靜態變數temp引用,而無法得到回收,這種對象保持就是我們所說的Java記憶體流失。總的來說,記憶體管理中的記憶體流失產生的主要原因:保留下來卻永遠不再使用的對象引用。

三. 幾種典型的記憶體流失

我們知道了在Java中確實會存在記憶體流失,那麼就讓我們看一看幾種典型的泄漏,並找出他們發生的原因和解決方案。

3.1 全域集合

在大型應用程式中存在各種各樣的全域資料倉庫是很普遍的,比如一個JNDI-tree或者一個session table。在這些情況下,必須注意管理儲存庫的大小。必須有某種機制從儲存庫中移除不再需要的資料。

通常有很多不同的解決形式,其中最常用的是一種周期啟動並執行清除作業。這個作業會驗證倉庫中的資料然後清除一切不需要的資料。

另一種管理儲存庫的方法是使用反向連結(referrer)計數。然後集合負責統計集合中每個入口的反向連結的數目。這要求反向連結告訴集合何時會退出入口。當反向連結數目為零時,該元素就可以從集合中移除了。

3.2 緩衝

緩衝一種用來快速尋找已經執行過的操作結果的資料結構。因此,如果一個操作執行需要比較多的資源並會多次被使用,通常做法是把常用的輸入資料的操作結果進行緩衝,以便在下次調用該操作時使用緩衝的資料。緩衝通常都是以動態方式實現的,如果緩衝設定不正確而大量使用緩衝的話則會出現記憶體溢出的後果,因此需要將所使用的記憶體容量與檢索資料的速度加以平衡。

常用的解決途徑是使用java.lang.ref.SoftReference類堅持將對象放入緩衝。這個方法可以保證當虛擬機器用完記憶體或者需要更多堆的時候,可以釋放這些對象的引用。

3.3 類裝載器

Java類裝載器的使用為記憶體流失提供了許多可乘之機。一般來說類裝載器都具有複雜結構,因為類裝載器不僅僅是只與"常規"對象引用有關,同時也和對象內部的引用有關。比如資料變數,方法和各種類。這意味著只要存在對資料變數,方法,各種類和對象的類裝載器,那麼類裝載器將駐留在JVM中。既然類裝載器可以同很多的類關聯,同時也可以和待用資料變數關聯,那麼相當多的記憶體就可能發生泄漏。

四. 如何檢測和處理記憶體流失

如何尋找引起記憶體流失的原因一般有兩個步驟:第一是安排有經驗的編程人員對代碼進行走查和分析,找出記憶體流失發生的位置;第二是使用專門的記憶體流失測試載入器進行測試。

第一個步驟在代碼走查的工作中,可以安排對系統業務和開發語言工具比較熟悉的開發人員對應用的代碼進行了交叉走查,盡量找出代碼中存在的資料庫連接聲明和結果集未關閉、代碼冗餘等故障代碼。

第二個步驟就是檢測Java的記憶體流失。在這裡我們通常使用一些工具來檢查Java程式的記憶體流失問題。市場上已有幾種專業檢查Java記憶體流失的工具,它們的基本工作原理大同小異,都是通過監測Java程式運行時,所有對象的申請、釋放等動作,將記憶體管理的所有資訊進行統計、分析、可視化。開發人員將根據這些資訊判斷程式是否有記憶體流失問題。這些工具包括Optimizeit Profiler,JProbe Profiler,JinSight , Rational 公司的Purify等。

4.1檢測記憶體流失的存在
這裡我們將簡單介紹我們在使用Optimizeit檢查的過程。通常在知道發生記憶體流失之後,第一步是要弄清楚泄漏了什麼資料和哪個類的對象引起了泄漏。

一般說來,一個正常的系統在其運行穩定後其記憶體的佔用量是基本穩定的,不應該是無限制的增長的。同樣,對任何一個類的對象的使用個數也有一個相對穩定的上限,不應該是持續增長的。根據這樣的基本假設,我們持續地觀察系統運行時使用的記憶體的大小和各執行個體的個數,如果記憶體的大小持續地增長,則說明系統存在記憶體流失,如果特定類的執行個體對象個數隨時間而增長(就是所謂的“增長率”),則說明這個類的執行個體可能存在泄漏情況。

另一方面通常發生記憶體流失的第一個跡象是:在應用程式中出現了OutOfMemoryError。在這種情況下,需要使用一些開銷較低的工具來監控和尋找記憶體流失。雖然OutOfMemoryError也有可能應用程式確實正在使用這麼多的記憶體;對於這種情況則可以增加JVM可用的堆的數量,或者對應用程式進行某種更改,使它使用較少的記憶體。

但是,在許多情況下,OutOfMemoryError都是記憶體流失的訊號。一種查明方法是不間斷地監控GC的活動,確定記憶體使用量量是否隨著時間增加。如果確實如此,就可能發生了記憶體流失。1


4.2處理記憶體流失的方法

一旦知道確實發生了記憶體流失,就需要更專業的工具來查明為什麼會發生泄漏。JVM自己是不會告訴您的。這些專業工具從JVM獲得記憶體系統資訊的方法基本上有兩種:JVMTI和位元組碼技術(byte code instrumentation)。Java虛擬機器工具介面(Java Virtual Machine Tools Interface,JVMTI)及其前身Java虛擬機器監視程式介面(Java Virtual Machine Profiling Interface,JVMPI)是外部工具與JVM通訊並從JVM收集資訊的標準化介面。位元組碼技術是指使用探測器處理位元組碼以獲得工具所需的資訊的技術。

Optimizeit是Borland公司的產品,主要用於協助對軟體系統進行代碼最佳化和故障診斷,其中的Optimizeit Profiler主要用於記憶體流失的分析。Profiler的堆視圖就是用來觀察系統運行使用的記憶體大小和各個類的執行個體分配的個數的。

首先,Profiler會進行趨勢分析,找出是哪個類的對象在泄漏。系統運行長時間後可以得到四個記憶體快照。對這四個記憶體快照進行綜合分析,如果每一次快照的記憶體使用量都比上一次有增長,可以認定系統存在記憶體流失,找出在四個快照中執行個體個數都保持增長的類,這些類可以初步被認定為存在泄漏。通過資料收集和初步分析,可以得出初步結論:系統是否存在記憶體流失和哪些對象存在泄漏(被泄漏)。

接下來,看看有哪些其他的類與泄漏的類的對象相關聯。前面已經談到Java中的記憶體流失就是無用的對象保持,簡單地說就是因為編碼的錯誤導致了一條本來不應該存在的引用鏈的存在(從而導致了被引用的對象無法釋放),因此記憶體流失分析的任務就是找出這條多餘的引用鏈,並找到其形成的原因。查看對象分配到哪裡是很有用的。同時只知道它們如何與其他對象相關聯(即哪些對象引用了它們)是不夠的,關於它們在何處建立的資訊也很有用。

最後,進一步研究單個對象,看看它們是如何互相關聯的。藉助於Profiler工具,應用程式中的代碼可以在分配時進行動態添加,以建立堆疊追蹤。也有可以對系統中所有對象分配進行動態堆疊追蹤。這些堆疊追蹤可以在工具中進行累積和分析。對每個被泄漏的執行個體對象,必然存在一條從某個牽引對象出發到達該對象的引用鏈。處於堆棧空間的牽引對象在被從棧中彈出後就失去其牽引的能力,變為非牽引對象。因此,在長時間的運行後,被泄露的對象基本上都是被作為類的靜態變數的牽引對象牽引。


總而言之, Java雖然有自動回收管理記憶體的功能,但記憶體流失也是不容忽視,它往往是破壞系統穩定性的重要因素。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.