Java內存泄露分析和解決方案及Windows自帶查看工具

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

做爲Internet最流行的編程語言之一,Java現正很是流行.咱們的網絡應用程序就主要採用Java語言開發,大致上分爲客戶端、服務器和數據庫三個層次.在進入測試過程當中,咱們發現有一個程序模塊系統內存和CPU資源消耗急劇增長,持續增加到出現java.lang.OutOfMemoryError爲止.通過分析Java內存泄漏是破壞系統的主要因素.這裏與你們分享咱們在開發過程當中遇到的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.session

實際上這些對象已是無用的,但還被引用,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的活動,肯定內存使用量是否隨着時間增長.若是確實如此,就可能發生了內存泄漏.

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雖然有自動回收管理內存的功能,但內存泄漏也是不容忽視,它每每是破壞系統穩定性的重要因素.

 

 

Windows自帶的Java內存查看命令

jinfo:能夠輸出並修改運行時的java 進程的opts。 
jps:與unix上的ps相似,用來顯示本地的java進程,能夠查看本地運行着幾個java程序,並顯示他們的進程號。 
jstat:一個極強的監視VM內存工具。能夠用來監視VM內存內的各類堆和非堆的大小及其內存使用量。 
jmap:打印出某個java進程(使用pid)內存內的全部'對象'的狀況(如:產生那些對象,及其數量)。 
jconsole:一個java GUI監視工具,能夠以圖表化的形式顯示各類數據。並可經過遠程鏈接監視遠程的服務器VM。 

詳細:在使用這些工具前,先用JPS命令獲取當前的每一個JVM進程號,而後選擇要查看的JVM。 
jstat工具特別強大,有衆多的可選項,詳細查看堆內各個部分的使用量,以及加載類的數量。使用時,需加上查看進程的進程id,和所選參數。如下詳細介紹各個參數的意義。 
jstat -class pid:顯示加載class的數量,及所佔空間等信息。 
jstat -compiler pid:顯示VM實時編譯的數量等信息。 
jstat -gc pid:能夠顯示gc的信息,查看gc的次數,及時間。其中最後五項,分別是young gc的次數,young gc的時間,full gc的次數,full gc的時間,gc的總時間。 
jstat -gccapacity:能夠顯示,VM內存中三代(young,old,perm)對象的使用和佔用大小,如:PGCMN顯示的是最小perm的內存使用量,PGCMX顯示的是perm的內存最大使用量,PGC是當前新生成的perm內存佔用量,PC是但前perm內存佔用量。其餘的能夠根據這個類推, OC是old內純的佔用量。 
jstat -gcnew pid:new對象的信息。 
jstat -gcnewcapacity pid:new對象的信息及其佔用量。 
jstat -gcold pid:old對象的信息。 
jstat -gcoldcapacity pid:old對象的信息及其佔用量。 
jstat -gcpermcapacity pid: perm對象的信息及其佔用量。 
jstat -util pid:統計gc信息統計。 
jstat -printcompilation pid:當前VM執行的信息。 
除了以上一個參數外,還能夠同時加上 兩個數字,如:jstat -printcompilation 3024 250 6是每250毫秒打印一次,一共打印6次,還能夠加上-h3每三行顯示一下標題。 

jmap是一個能夠輸出全部內存中對象的工具,甚至能夠將VM 中的heap,以二進制輸出成文本。 
命令:jmap -dump:format=b,file=heap.bin <pid> 
file:保存路徑及文件名 
pid:進程編號 
•jmap -histo:live  pid| less :堆中活動的對象以及大小 
•jmap -heap pid : 查看堆的使用情況信息 


jinfo:的用處比較簡單,就是能輸出並修改運行時的java進程的運行參數。用法是jinfo -opt pid 如:查看2788的MaxPerm大小能夠用 jinfo -flag MaxPermSize 2788。 

jconsole是一個用java寫的GUI程序,用來監控VM,並可監控遠程的VM,很是易用,並且功能很是強。使用方法:命令行裏打 jconsole,選則進程就能夠了。 



JConsole中關於內存分區的說明。 

Eden Space (heap): 內存最初從這個線程池分配給大部分對象。 
Survivor Space (heap):用於保存在eden space內存池中通過垃圾回收後沒有被回收的對象。 
Tenured Generation (heap):用於保持已經在 survivor space內存池中存在了一段時間的對象。 
Permanent Generation (non-heap): 保存虛擬機本身的靜態(refective)數據,例如類(class)和方法(method)對象。Java虛擬機共享這些類數據。這個區域被分割爲只讀的和只寫的, 
Code Cache (non-heap):HotSpot Java虛擬機包括一個用於編譯和保存本地代碼(native code)的內存,叫作「代碼緩存區」(code cache) 

•jstack ( 查看jvm線程運行狀態,是否有死鎖現象等等信息) : jstack pid : thread dump 
•jstat -gcutil  pid  1000 100  : 1000ms統計一次gc狀況統計100次; 

另外推薦一款查看jmap dump 的內存對象工具 MemoryAnalyzer 
網址:http://www.eclipse.org/mat/,能夠查看dump時對象數量,內存佔用,線程狀況等。

以及使用Memory Analyzer tool分析內存泄露

http://www.blogjava.net/rosen/archive/2010/05/21/321575.html

相關文章
相關標籤/搜索