JAVA內(nèi)存泄漏的示例分析-創(chuàng)新互聯(lián)

本篇文章給大家分享的是有關(guān)JAVA內(nèi)存泄漏的示例分析,小編覺(jué)得挺實(shí)用的,因此分享給大家學(xué)習(xí),希望大家閱讀完這篇文章后可以有所收獲,話不多說(shuō),跟著小編一起來(lái)看看吧。

創(chuàng)新互聯(lián)公司一直在為企業(yè)提供服務(wù),多年的磨煉,使我們?cè)趧?chuàng)意設(shè)計(jì),全網(wǎng)整合營(yíng)銷(xiāo)推廣到技術(shù)研發(fā)擁有了開(kāi)發(fā)經(jīng)驗(yàn)。我們擅長(zhǎng)傾聽(tīng)企業(yè)需求,挖掘用戶對(duì)產(chǎn)品需求服務(wù)價(jià)值,為企業(yè)制作有用的創(chuàng)意設(shè)計(jì)體驗(yàn)。核心團(tuán)隊(duì)擁有超過(guò)十年以上行業(yè)經(jīng)驗(yàn),涵蓋創(chuàng)意,策化,開(kāi)發(fā)等專(zhuān)業(yè)領(lǐng)域,公司涉及領(lǐng)域有基礎(chǔ)互聯(lián)網(wǎng)服務(wù)雙線服務(wù)器托管、成都app軟件開(kāi)發(fā)、手機(jī)移動(dòng)建站、網(wǎng)頁(yè)設(shè)計(jì)、網(wǎng)絡(luò)整合營(yíng)銷(xiāo)。

Java內(nèi)存泄漏是每個(gè)Java程序員都會(huì)遇到的問(wèn)題,程序在本地運(yùn)行一切正常,可是布署到遠(yuǎn)端就會(huì)出現(xiàn)內(nèi)存無(wú)限制的增長(zhǎng),最后系統(tǒng)癱瘓,那么如何最快最好的檢測(cè)程序的穩(wěn)定性,防止系統(tǒng)崩盤(pán),作者用自已的親身經(jīng)歷與各位網(wǎng)友分享解決這些問(wèn)題的辦法。 

作為Internet最流行的編程語(yǔ)言之一,Java現(xiàn)正非常流行。我們的網(wǎng)絡(luò)應(yīng)用程序就主要采用Java語(yǔ)言開(kāi)發(fā),大體上分為客戶端、服務(wù)器和數(shù)據(jù)庫(kù)三個(gè)層次。在進(jìn)入測(cè)試過(guò)程中,我們發(fā)現(xiàn)有一個(gè)程序模塊系統(tǒng)內(nèi)存和CPU資源消耗急劇增加,持續(xù)增長(zhǎng)到出現(xiàn)java.lang.OutOfMemoryError為止。經(jīng)過(guò)分析Java內(nèi)存泄漏是破壞系統(tǒng)的主要因素。這里與大家分享我們?cè)陂_(kāi)發(fā)過(guò)程中遇到的Java內(nèi)存泄漏的檢測(cè)和處理解決過(guò)程. 

一. Java是如何管理內(nèi)存 

為了判斷Java中是否有內(nèi)存泄露,我們首先必須了解Java是如何管理內(nèi)存的。Java的內(nèi)存管理就是對(duì)象的分配和釋放問(wèn)題。在Java中,內(nèi)存的分配是由程序完成的,而內(nèi)存的釋放是由垃圾收集器(Garbage Collection,GC)完成的,程序員不需要通過(guò)調(diào)用函數(shù)來(lái)釋放內(nèi)存,但它只能回收無(wú)用并且不再被其它對(duì)象引用的那些對(duì)象所占用的空間。 

Java的內(nèi)存垃圾回收機(jī)制是從程序的主要運(yùn)行對(duì)象開(kāi)始檢查引用鏈,當(dāng)遍歷一遍后發(fā)現(xiàn)沒(méi)有被引用的孤立對(duì)象就作為垃圾回收。GC為了能夠正確釋放對(duì)象,必須監(jiān)控每一個(gè)對(duì)象的運(yùn)行狀態(tài),包括對(duì)象的申請(qǐng)、引用、被引用、賦值等,GC都需要進(jìn)行監(jiān)控。監(jiān)視對(duì)象狀態(tài)是為了更加準(zhǔn)確地、及時(shí)地釋放對(duì)象,而釋放對(duì)象的根本原則就是該對(duì)象不再被引用。 

在Java中,這些無(wú)用的對(duì)象都由GC負(fù)責(zé)回收,因此程序員不需要考慮這部分的內(nèi)存泄露。雖然,我們有幾個(gè)函數(shù)可以訪問(wèn)GC,例如運(yùn)行GC的函數(shù)System.gc(),但是根據(jù)Java語(yǔ)言規(guī)范定義,該函數(shù)不保證JVM的垃圾收集器一定會(huì)執(zhí)行。因?yàn)椴煌腏VM實(shí)現(xiàn)者可能使用不同的算法管理GC。通常GC的線程的優(yōu)先級(jí)別較低。JVM調(diào)用GC的策略也有很多種,有的是內(nèi)存使用到達(dá)一定程度時(shí),GC才開(kāi)始工作,也有定時(shí)執(zhí)行的,有的是平緩執(zhí)行GC,有的是中斷式執(zhí)行GC。但通常來(lái)說(shuō),我們不需要關(guān)心這些。 


一. 什么是Java中的內(nèi)存泄露 

導(dǎo)致內(nèi)存泄漏主要的原因是,先前申請(qǐng)了內(nèi)存空間而忘記了釋放。如果程序中存在對(duì)無(wú)用對(duì)象的引用,那么這些對(duì)象就會(huì)駐留內(nèi)存,消耗內(nèi)存,因?yàn)闊o(wú)法讓垃圾回收器GC驗(yàn)證這些對(duì)象是否不再需要。如果存在對(duì)象的引用,這個(gè)對(duì)象就被定義為"有效的活動(dòng)",同時(shí)不會(huì)被釋放。要確定對(duì)象所占內(nèi)存將被回收,我們就要?jiǎng)?wù)必確認(rèn)該對(duì)象不再會(huì)被使用。典型的做法就是把對(duì)象數(shù)據(jù)成員設(shè)為null或者從集合中移除該對(duì)象。但當(dāng)局部變量不需要時(shí),不需明顯的設(shè)為null,因?yàn)橐粋€(gè)方法執(zhí)行完畢時(shí),這些引用會(huì)自動(dòng)被清理。 

在Java中,內(nèi)存泄漏就是存在一些被分配的對(duì)象,這些對(duì)象有下面兩個(gè)特點(diǎn),首先,這些對(duì)象是有被引用的,即在有向樹(shù)形圖中,存在樹(shù)枝通路可以與其相連;其次,這些對(duì)象是無(wú)用的,即程序以后不會(huì)再使用這些對(duì)象。如果對(duì)象滿足這兩個(gè)條件,這些對(duì)象就可以判定為Java中的內(nèi)存泄漏,這些對(duì)象不會(huì)被GC所回收,然而它卻占用內(nèi)存。 

這里引用一個(gè)??吹降睦樱谙旅娴拇a中,循環(huán)申請(qǐng)Object對(duì)象,并將所申請(qǐng)的對(duì)象放入一個(gè)Vector中,如果僅僅釋放對(duì)象本身,但因?yàn)閂ector仍然引用該對(duì)象,所以這個(gè)對(duì)象對(duì)GC來(lái)說(shuō)是不可回收的。因此,如果對(duì)象加入到Vector后,還必須從Vector中刪除,最簡(jiǎn)單的方法就是將Vector對(duì)象設(shè)置為null。 

Vector v = new Vector(10);
for (int i = 1; i < 100; i++)
......{
 Object o = new Object();
 v.add(o);
 o = null;
}//此時(shí),所有的Object對(duì)象都沒(méi)有被釋放,因?yàn)樽兞縱引用這些對(duì)象。 


實(shí)際上這些對(duì)象已經(jīng)是無(wú)用的,但還被引用,GC就無(wú)能為力了(事實(shí)上GC認(rèn)為它還有用),這一點(diǎn)是導(dǎo)致內(nèi)存泄漏最重要的原因。 再引用另一個(gè)例子來(lái)說(shuō)明Java的內(nèi)存泄漏。假設(shè)有一個(gè)日志類(lèi)Logger,其提供一個(gè)靜態(tài)的log(String msg),任何其它類(lèi)都可以調(diào)用Logger.Log(message)來(lái)將message的內(nèi)容記錄到系統(tǒng)的日志文件中。

Logger類(lèi)有一個(gè)類(lèi)型為HashMap的靜態(tài)變量temp,每次在執(zhí)行l(wèi)og(message)的時(shí)候,都首先將message的值寫(xiě)入temp中(以當(dāng)前線程+當(dāng)前時(shí)間為鍵),在退出之前再?gòu)膖emp中將以當(dāng)前線程和當(dāng)前時(shí)間為鍵的條目刪除。注意,這里當(dāng)前時(shí)間是不斷變化的,所以log在退出之前執(zhí)行刪除條目的操作并不能刪除執(zhí)行之初寫(xiě)入的條目。這樣,任何一個(gè)作為參數(shù)傳給log的字符串最終由于被Logger的靜態(tài)變量temp引用,而無(wú)法得到回收,這種對(duì)象保持就是我們所說(shuō)的Java內(nèi)存泄漏。 

總的來(lái)說(shuō),內(nèi)存管理中的內(nèi)存泄漏產(chǎn)生的主要原因:保留下來(lái)卻永遠(yuǎn)不再使用的對(duì)象引用。

三. 幾種典型的內(nèi)存泄漏 

我們知道了在Java中確實(shí)會(huì)存在內(nèi)存泄漏,那么就讓我們看一看幾種典型的泄漏,并找出他們發(fā)生的原因和解決方法。 

3.1 全局集合 
在大型應(yīng)用程序中存在各種各樣的全局?jǐn)?shù)據(jù)倉(cāng)庫(kù)是很普遍的,比如一個(gè)JNDI-tree或者一個(gè)session table。在這些情況下,必須注意管理儲(chǔ)存庫(kù)的大小。必須有某種機(jī)制從儲(chǔ)存庫(kù)中移除不再需要的數(shù)據(jù)。 

通常有很多不同的解決形式,其中最常用的是一種周期運(yùn)行的清除作業(yè)。這個(gè)作業(yè)會(huì)驗(yàn)證倉(cāng)庫(kù)中的數(shù)據(jù)然后清除一切不需要的數(shù)據(jù)。 

另一種管理儲(chǔ)存庫(kù)的方法是使用反向鏈接(referrer)計(jì)數(shù)。然后集合負(fù)責(zé)統(tǒng)計(jì)集合中每個(gè)入口的反向鏈接的數(shù)目。這要求反向鏈接告訴集合何時(shí)會(huì)退出入口。當(dāng)反向鏈接數(shù)目為零時(shí),該元素就可以從集合中移除了。 
   
3.2 緩存 
緩存一種用來(lái)快速查找已經(jīng)執(zhí)行過(guò)的操作結(jié)果的數(shù)據(jù)結(jié)構(gòu)。因此,如果一個(gè)操作執(zhí)行需要比較多的資源并會(huì)多次被使用,通常做法是把常用的輸入數(shù)據(jù)的操作結(jié)果進(jìn)行緩存,以便在下次調(diào)用該操作時(shí)使用緩存的數(shù)據(jù)。緩存通常都是以動(dòng)態(tài)方式實(shí)現(xiàn)的,如果緩存設(shè)置不正確而大量使用緩存的話則會(huì)出現(xiàn)內(nèi)存溢出的后果,因此需要將所使用的內(nèi)存容量與檢索數(shù)據(jù)的速度加以平衡。 

常用的解決途徑是使用java.lang.ref.SoftReference類(lèi)堅(jiān)持將對(duì)象放入緩存。這個(gè)方法可以保證當(dāng)虛擬機(jī)用完內(nèi)存或者需要更多堆的時(shí)候,可以釋放這些對(duì)象的引用。 

3.3 類(lèi)裝載器 
Java類(lèi)裝載器的使用為內(nèi)存泄漏提供了許多可乘之機(jī)。一般來(lái)說(shuō)類(lèi)裝載器都具有復(fù)雜結(jié)構(gòu),因?yàn)轭?lèi)裝載器不僅僅是只與"常規(guī)"對(duì)象引用有關(guān),同時(shí)也和對(duì)象內(nèi)部的引用有關(guān)。比如數(shù)據(jù)變量,方法和各種類(lèi)。這意味著只要存在對(duì)數(shù)據(jù)變量,方法,各種類(lèi)和對(duì)象的類(lèi)裝載器,那么類(lèi)裝載器將駐留在JVM中。既然類(lèi)裝載器可以同很多的類(lèi)關(guān)聯(lián),同時(shí)也可以和靜態(tài)數(shù)據(jù)變量關(guān)聯(lián),那么相當(dāng)多的內(nèi)存就可能發(fā)生泄漏。 


四. 如何檢測(cè)和處理內(nèi)存泄漏 
如何查找引起內(nèi)存泄漏的原因一般有兩個(gè)步驟:第一是安排有經(jīng)驗(yàn)的編程人員對(duì)代碼進(jìn)行走查和分析,找出內(nèi)存泄漏發(fā)生的位置;第二是使用專(zhuān)門(mén)的內(nèi)存泄漏測(cè)試工具進(jìn)行測(cè)試。 

第一個(gè)步驟在代碼走查的工作中,可以安排對(duì)系統(tǒng)業(yè)務(wù)和開(kāi)發(fā)語(yǔ)言工具比較熟悉的開(kāi)發(fā)人員對(duì)應(yīng)用的代碼進(jìn)行了交叉走查,盡量找出代碼中存在的數(shù)據(jù)庫(kù)連接聲明和結(jié)果集未關(guān)閉、代碼冗余等故障代碼。 

第二個(gè)步驟就是檢測(cè)Java的內(nèi)存泄漏。在這里我們通常使用一些工具來(lái)檢查Java程序的內(nèi)存泄漏問(wèn)題。市場(chǎng)上已有幾種專(zhuān)業(yè)檢查Java內(nèi)存泄漏的工具,它們的基本工作原理大同小異,都是通過(guò)監(jiān)測(cè)Java程序運(yùn)行時(shí),所有對(duì)象的申請(qǐng)、釋放等動(dòng)作,將內(nèi)存管理的所有信息進(jìn)行統(tǒng)計(jì)、分析、可視化。開(kāi)發(fā)人員將根據(jù)這些信息判斷程序是否有內(nèi)存泄漏問(wèn)題。這些工具包括Optimizeit Profiler,JProbe Profiler,JinSight , Rational 公司的Purify等。 

4.1檢測(cè)內(nèi)存泄漏的存在 
這里我們將簡(jiǎn)單介紹我們?cè)谑褂肙ptimizeit檢查的過(guò)程。通常在知道發(fā)生內(nèi)存泄漏之后,第一步是要弄清楚泄漏了什么數(shù)據(jù)和哪個(gè)類(lèi)的對(duì)象引起了泄漏。 

一般說(shuō)來(lái),一個(gè)正常的系統(tǒng)在其運(yùn)行穩(wěn)定后其內(nèi)存的占用量是基本穩(wěn)定的,不應(yīng)該是無(wú)限制的增長(zhǎng)的。同樣,對(duì)任何一個(gè)類(lèi)的對(duì)象的使用個(gè)數(shù)也有一個(gè)相對(duì)穩(wěn)定的上限,不應(yīng)該是持續(xù)增長(zhǎng)的。根據(jù)這樣的基本假設(shè),我們持續(xù)地觀察系統(tǒng)運(yùn)行時(shí)使用的內(nèi)存的大小和各實(shí)例的個(gè)數(shù),如果內(nèi)存的大小持續(xù)地增長(zhǎng),則說(shuō)明系統(tǒng)存在內(nèi)存泄漏,如果特定類(lèi)的實(shí)例對(duì)象個(gè)數(shù)隨時(shí)間而增長(zhǎng)(就是所謂的“增長(zhǎng)率”),則說(shuō)明這個(gè)類(lèi)的實(shí)例可能存在泄漏情況。 

另一方面通常發(fā)生內(nèi)存泄漏的第一個(gè)跡象是:在應(yīng)用程序中出現(xiàn)了OutOfMemoryError。在這種情況下,需要使用一些開(kāi)銷(xiāo)較低的工具來(lái)監(jiān)控和查找內(nèi)存泄漏。雖然OutOfMemoryError也有可能應(yīng)用程序確實(shí)正在使用這么多的內(nèi)存;對(duì)于這種情況則可以增加JVM可用的堆的數(shù)量,或者對(duì)應(yīng)用程序進(jìn)行某種更改,使它使用較少的內(nèi)存。 

但是,在許多情況下,OutOfMemoryError都是內(nèi)存泄漏的信號(hào)。一種查明方法是不間斷地監(jiān)控GC的活動(dòng),確定內(nèi)存使用量是否隨著時(shí)間增加。如果確實(shí)如此,就可能發(fā)生了內(nèi)存泄漏。

4.2處理內(nèi)存泄漏的方法 

一旦知道確實(shí)發(fā)生了內(nèi)存泄漏,就需要更專(zhuān)業(yè)的工具來(lái)查明為什么會(huì)發(fā)生泄漏。JVM自己是不會(huì)告訴您的。這些專(zhuān)業(yè)工具從JVM獲得內(nèi)存系統(tǒng)信息的方法基本上有兩種:JVMTI和字節(jié)碼技術(shù)(byte code instrumentation)。Java虛擬機(jī)工具接口(Java Virtual Machine Tools Interface,JVMTI)及其前身Java虛擬機(jī)監(jiān)視程序接口(Java Virtual Machine Profiling Interface,JVMPI)是外部工具與JVM通信并從JVM收集信息的標(biāo)準(zhǔn)化接口。字節(jié)碼技術(shù)是指使用探測(cè)器處理字節(jié)碼以獲得工具所需的信息的技術(shù)。 

Optimizeit是Borland公司的產(chǎn)品,主要用于協(xié)助對(duì)軟件系統(tǒng)進(jìn)行代碼優(yōu)化和故障診斷,其中的Optimizeit Profiler主要用于內(nèi)存泄漏的分析。Profiler的堆視圖就是用來(lái)觀察系統(tǒng)運(yùn)行使用的內(nèi)存大小和各個(gè)類(lèi)的實(shí)例分配的個(gè)數(shù)的。 

首先,Profiler會(huì)進(jìn)行趨勢(shì)分析,找出是哪個(gè)類(lèi)的對(duì)象在泄漏。系統(tǒng)運(yùn)行長(zhǎng)時(shí)間后可以得到四個(gè)內(nèi)存快照。對(duì)這四個(gè)內(nèi)存快照進(jìn)行綜合分析,如果每一次快照的內(nèi)存使用都比上一次有增長(zhǎng),可以認(rèn)定系統(tǒng)存在內(nèi)存泄漏,找出在四個(gè)快照中實(shí)例個(gè)數(shù)都保持增長(zhǎng)的類(lèi),這些類(lèi)可以初步被認(rèn)定為存在泄漏。通過(guò)數(shù)據(jù)收集和初步分析,可以得出初步結(jié)論:系統(tǒng)是否存在內(nèi)存泄漏和哪些對(duì)象存在泄漏(被泄漏)。 

接下來(lái),看看有哪些其他的類(lèi)與泄漏的類(lèi)的對(duì)象相關(guān)聯(lián)。前面已經(jīng)談到Java中的內(nèi)存泄漏就是無(wú)用的對(duì)象保持,簡(jiǎn)單地說(shuō)就是因?yàn)榫幋a的錯(cuò)誤導(dǎo)致了一條本來(lái)不應(yīng)該存在的引用鏈的存在(從而導(dǎo)致了被引用的對(duì)象無(wú)法釋放),因此內(nèi)存泄漏分析的任務(wù)就是找出這條多余的引用鏈,并找到其形成的原因。查看對(duì)象分配到哪里是很有用的。同時(shí)只知道它們?nèi)绾闻c其他對(duì)象相關(guān)聯(lián)(即哪些對(duì)象引用了它們)是不夠的,關(guān)于它們?cè)诤翁巹?chuàng)建的信息也很有用。 

最后,進(jìn)一步研究單個(gè)對(duì)象,看看它們是如何互相關(guān)聯(lián)的。借助于Profiler工具,應(yīng)用程序中的代碼可以在分配時(shí)進(jìn)行動(dòng)態(tài)添加,以創(chuàng)建堆棧跟蹤。也有可以對(duì)系統(tǒng)中所有對(duì)象分配進(jìn)行動(dòng)態(tài)的堆棧跟蹤。這些堆棧跟蹤可以在工具中進(jìn)行累積和分析。對(duì)每個(gè)被泄漏的實(shí)例對(duì)象,必然存在一條從某個(gè)牽引對(duì)象出發(fā)到達(dá)該對(duì)象的引用鏈。處于堆棧空間的牽引對(duì)象在被從棧中彈出后就失去其牽引的能力,變?yōu)榉菭恳龑?duì)象。因此,在長(zhǎng)時(shí)間的運(yùn)行后,被泄露的對(duì)象基本上都是被作為類(lèi)的靜態(tài)變量的牽引對(duì)象牽引。 

總而言之, Java雖然有自動(dòng)回收管理內(nèi)存的功能,但內(nèi)存泄漏也是不容忽視,它往往是破壞系統(tǒng)穩(wěn)定性的重要因素。

以上就是JAVA內(nèi)存泄漏的示例分析,小編相信有部分知識(shí)點(diǎn)可能是我們?nèi)粘9ぷ鲿?huì)見(jiàn)到或用到的。希望你能通過(guò)這篇文章學(xué)到更多知識(shí)。更多詳情敬請(qǐng)關(guān)注創(chuàng)新互聯(lián)-成都網(wǎng)站建設(shè)公司行業(yè)資訊頻道。

新聞名稱(chēng):JAVA內(nèi)存泄漏的示例分析-創(chuàng)新互聯(lián)
文章路徑:http://bm7419.com/article32/ggepc.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供響應(yīng)式網(wǎng)站、外貿(mào)網(wǎng)站建設(shè)、搜索引擎優(yōu)化、網(wǎng)站建設(shè)、外貿(mào)建站、面包屑導(dǎo)航

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)

營(yíng)銷(xiāo)型網(wǎng)站建設(shè)