熱點推薦:
您现在的位置: 電腦知識網 >> 編程 >> Java編程 >> Java核心技術 >> 正文

Java內存洩漏分析與解決方案

2013-11-23 19:33:11  來源: Java核心技術 

  Java內存洩漏是每個Java程序員都會遇到的問題程序在本地運行一切正常可是布署到遠端就會出現內存無限制的增長最後系統癱瘓那麼如何最快最好的檢測程序的穩定性防止系統崩盤作者用自已的親身經歷與各位網友分享解決這些問題的辦法

  作為Internet最流行的編程語言之一Java現正非常流行我們的網絡應用程序就主要采用Java語言開發大體上分為客戶端服務器和數據庫三個層次在進入測試過程中我們發現有一個程序模塊系統內存和CPU資源消耗急劇增加持續增長到出現javalangOutOfMemoryError為止經過分析Java內存洩漏是破壞系統的主要因素這裡與大家分享我們在開發過程中遇到的Java內存洩漏的檢測和處理解決過程

  一 Java是如何管理內存

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

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

  在Java中這些無用的對象都由GC負責回收因此程序員不需要考慮這部分的內存洩露雖然我們有幾個函數可以訪問GC例如運行GC的函數Systemgc()但是根據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();for (int i = ; i < ; i++){ Object o = new Object(); vadd(o); o = null;}//此時所有的Object對象都沒有被釋放因為變量v引用這些對象

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

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

  三 幾種典型的內存洩漏

  我們知道了在Java中確實會存在內存洩漏那麼就讓我們看一看幾種典型的洩漏並找出他們發生的原因和解決方法

   全局集合

  在大型應用程序中存在各種各樣的全局數據倉庫是很普遍的比如一個JNDItree或者一個session table在這些情況下必須注意管理儲存庫的大小必須有某種機制從儲存庫中移除不再需要的數據

  通常有很多不同的解決形式其中最常用的是一種周期運行的清除作業這個作業會驗證倉庫中的數據然後清除一切不需要的數據

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

   緩存

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

  常用的解決途徑是使用javalangrefSoftReference類堅持將對象放入緩存這個方法可以保證當虛擬機用完內存或者需要更多堆的時候可以釋放這些對象的引用

   類裝載器

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

  四 如何檢測和處理內存洩漏

  如何查找引起內存洩漏的原因一般有兩個步驟:第一是安排有經驗的編程人員對代碼進行走查和分析找出內存洩漏發生的位置;第二是使用專門的內存洩漏測試工具進行測試

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

  第二個步驟就是檢測Java的內存洩漏在這裡我們通常使用一些工具來檢查Java程序的內存洩漏問題市場上已有幾種專業檢查Java內存洩漏的工具它們的基本工作原理大同小異都是通過監測Java程序運行時所有對象的申請釋放等動作將內存管理的所有信息進行統計分析可視化開發人員將根據這些信息判斷程序是否有內存洩漏問題這些工具包括Optimizeit ProfilerJProbe ProfilerJinSight Rational 公司的Purify等

  檢測內存洩漏的存在

  這裡我們將簡單介紹我們在使用Optimizeit檢查的過程通常在知道發生內存洩漏之後第一步是要弄清楚洩漏了什麼數據和哪個類的對象引起了洩漏

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

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

  但是在許多情況下OutOfMemoryError都是內存洩漏的信號一種查明方法是不間斷地監控GC的活動確定內存使用量是否隨著時間增加如果確實如此就可能發生了內存洩漏

  處理內存洩漏的方法

  一旦知道確實發生了內存洩漏就需要更專業的工具來查明為什麼會發生洩漏JVM自己是不會告訴您的這些專業工具從JVM獲得內存系統信息的方法基本上有兩種JVMTI和字節碼技術(byte code instrumentation)Java虛擬機工具接口(Java Virtual Machine Tools InterfaceJVMTI)及其前身Java虛擬機監視程序接口(Java Virtual Machine Profiling InterfaceJVMPI)是外部工具與JVM通信並從JVM收集信息的標准化接口字節碼技術是指使用探測器處理字節碼以獲得工具所需的信息的技術

  Optimizeit是Borland公司的產品主要用於協助對軟件系統進行代碼優化和故障診斷其中的Optimizeit Profiler主要用於內存洩漏的分析Profiler的堆視圖就是用來觀察系統運行使用的內存大小和各個類的實例分配的個數的

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

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

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

  總而言之 Java雖然有自動回收管理內存的功能但內存洩漏也是不容忽視它往往是破壞系統穩定性的重要因素


From:http://tw.wingwit.com/Article/program/Java/hx/201311/27080.html
    推薦文章
    Copyright © 2005-2013 電腦知識網 Computer Knowledge   All rights reserved.