天天看點

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的活動,确定記憶體使用量是否随着時間增加。如果确實如此,就可能發生了記憶體洩漏。

  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雖然有自動回收管理記憶體的功能,但記憶體洩漏也是不容忽視,它往往是破壞系統穩定性的重要因素。