java代碼內(nèi)存泄露 java中的內(nèi)存泄露是怎么回事

java中什么是內(nèi)存泄露

一、Java內(nèi)存回收機制

創(chuàng)新互聯(lián)公司是一家以網(wǎng)站建設(shè)公司、網(wǎng)頁設(shè)計、品牌設(shè)計、軟件運維、成都網(wǎng)站推廣、小程序App開發(fā)等移動開發(fā)為一體互聯(lián)網(wǎng)公司。已累計為水電改造等眾行業(yè)中小客戶提供優(yōu)質(zhì)的互聯(lián)網(wǎng)建站和軟件開發(fā)服務(wù)。

不論哪種語言的內(nèi)存分配方式,都需要返回所分配內(nèi)存的真實地址,也就是返回一個指針到內(nèi)存塊的首地址。Java中對象是采用new或者反射的方法創(chuàng)建的,這些對象的創(chuàng)建都是在堆(Heap)中分配的,所有對象的回收都是由Java虛擬機通過垃圾回收機制完成的。GC為了能夠正確釋放對象,會監(jiān)控每個對象的運行狀況,對他們的申請、引用、被引用、賦值等狀況進行監(jiān)控,Java會使用有向圖的方法進行管理內(nèi)存,實時監(jiān)控對象是否可以達到,如果不可到達,則就將其回收,這樣也可以消除引用循環(huán)的問題。在Java語言中,判斷一個內(nèi)存空間是否符合垃圾收集標(biāo)準(zhǔn)有兩個:一個是給對象賦予了空值null,以下再沒有調(diào)用過,另一個是給對象賦予了新值,這樣重新分配了內(nèi)存空間。

二、Java內(nèi)存泄露引起原因

首先,什么是內(nèi)存泄露?經(jīng)常聽人談起內(nèi)存泄露,但要問什么是內(nèi)存泄露,沒幾個說得清楚。內(nèi)存泄露是指無用對象(不再使用的對象)持續(xù)占有內(nèi)存或無用對象的內(nèi)存得不到及時釋放,從而造成的內(nèi)存空間的浪費稱為內(nèi)存泄露。內(nèi)存泄露有時不嚴(yán)重且不易察覺,這樣開發(fā)者就不知道存在內(nèi)存泄露,但有時也會很嚴(yán)重,會提示你Out of memory。

那么,Java內(nèi)存泄露根本原因是什么呢?長生命周期的對象持有短生命周期對象的引用就很可能發(fā)生內(nèi)存泄露,盡管短生命周期對象已經(jīng)不再需要,但是因為長生命周期對象持有它的引用而導(dǎo)致不能被回收,這就是java中內(nèi)存泄露的發(fā)生場景。具體主要有如下幾大類:

1、靜態(tài)集合類引起內(nèi)存泄露:

像HashMap、Vector等的使用最容易出現(xiàn)內(nèi)存泄露,這些靜態(tài)變量的生命周期和應(yīng)用程序一致,他們所引用的所有的對象Object也不能被釋放,因為他們也將一直被Vector等引用著。

例:

Static Vector v = new Vector(10);

for (int i = 1; i100; i++)

{

Object o = new Object();

v.add(o);

o = null;

}//

在這個例子中,循環(huán)申請Object 對象,并將所申請的對象放入一個Vector 中,如果僅僅釋放引用本身(o=null),那么Vector 仍然引用該對象,所以這個對象對GC 來說是不可回收的。因此,如果對象加入到Vector 后,還必須從Vector 中刪除,最簡單的方法就是將Vector對象設(shè)置為null。

2、當(dāng)集合里面的對象屬性被修改后,再調(diào)用remove()方法時不起作用。

例:

public static void main(String[] args)

{

SetPerson set = new HashSetPerson();

Person p1 = new Person("唐僧","pwd1",25);

Person p2 = new Person("孫悟空","pwd2",26);

Person p3 = new Person("豬八戒","pwd3",27);

set.add(p1);

set.add(p2);

set.add(p3);

System.out.println("總共有:"+set.size()+" 個元素!"); //結(jié)果:總共有:3 個元素!

p3.setAge(2); //修改p3的年齡,此時p3元素對應(yīng)的hashcode值發(fā)生改變

set.remove(p3); //此時remove不掉,造成內(nèi)存泄漏

set.add(p3); //重新添加,居然添加成功

System.out.println("總共有:"+set.size()+" 個元素!"); //結(jié)果:總共有:4 個元素!

for (Person person : set)

{

System.out.println(person);

}

}

3、監(jiān)聽器

在java 編程中,我們都需要和監(jiān)聽器打交道,通常一個應(yīng)用當(dāng)中會用到很多監(jiān)聽器,我們會調(diào)用一個控件的諸如addXXXListener()等方法來增加監(jiān)聽器,但往往在釋放對象的時候卻沒有記住去刪除這些監(jiān)聽器,從而增加了內(nèi)存泄漏的機會。

4、各種連接

比如數(shù)據(jù)庫連接(dataSourse.getConnection()),網(wǎng)絡(luò)連接(socket)和io連接,除非其顯式的調(diào)用了其close()方法將其連接關(guān)閉,否則是不會自動被GC 回收的。對于Resultset 和Statement 對象可以不進行顯式回收,但Connection 一定要顯式回收,因為Connection 在任何時候都無法自動回收,而Connection一旦回收,Resultset 和Statement 對象就會立即為NULL。但是如果使用連接池,情況就不一樣了,除了要顯式地關(guān)閉連接,還必須顯式地關(guān)閉Resultset Statement 對象(關(guān)閉其中一個,另外一個也會關(guān)閉),否則就會造成大量的Statement 對象無法釋放,從而引起內(nèi)存泄漏。這種情況下一般都會在try里面去的連接,在finally里面釋放連接。

5、內(nèi)部類和外部模塊等的引用

內(nèi)部類的引用是比較容易遺忘的一種,而且一旦沒釋放可能導(dǎo)致一系列的后繼類對象沒有釋放。此外程序員還要小心外部模塊不經(jīng)意的引用,例如程序員A 負責(zé)A 模塊,調(diào)用了B 模塊的一個方法如:

public void registerMsg(Object b);

這種調(diào)用就要非常小心了,傳入了一個對象,很可能模塊B就保持了對該對象的引用,這時候就需要注意模塊B 是否提供相應(yīng)的操作去除引用。

6、單例模式

不正確使用單例模式是引起內(nèi)存泄露的一個常見問題,單例對象在被初始化后將在JVM的整個生命周期中存在(以靜態(tài)變量的方式),如果單例對象持有外部對象的引用,那么這個外部對象將不能被jvm正?;厥?,導(dǎo)致內(nèi)存泄露,考慮下面的例子:

class A{

public A(){

B.getInstance().setA(this);

}

....

}

//B類采用單例模式

class B{

private A a;

private static B instance=new B();

public B(){}

public static B getInstance(){

return instance;

}

public void setA(A a){

this.a=a;

}

//getter...

}

顯然B采用singleton模式,它持有一個A對象的引用,而這個A類的對象將不能被回收。想象下如果A是個比較復(fù)雜的對象或者集合類型會發(fā)生什么情況

java是否有內(nèi)存泄露和內(nèi)存溢出

java中的內(nèi)存溢出和內(nèi)存泄漏

內(nèi)存溢出:

對于整個應(yīng)用程序來說,JVM內(nèi)存空間,已經(jīng)沒有多余的空間分配給新的對象。所以就發(fā)生內(nèi)存溢出。

內(nèi)存泄露:

在應(yīng)用的整個生命周期內(nèi),某個對象一直存在,且對象占用的內(nèi)存空間越來越大,最終導(dǎo)致JVM內(nèi)存泄露,

比如:緩存的應(yīng)用,如果不設(shè)置上限的話,緩存的容量可能會一直增長。

靜態(tài)集合引用,如果該集合存放了無數(shù)個對象,隨著時間的推移也有可能使容量無限制的增長,最終導(dǎo)致JVM內(nèi)存泄露。

內(nèi)存泄露,是應(yīng)用程序中的某個對象長時間的存活,并且占用空間不斷增長,最終導(dǎo)致內(nèi)存泄露。

是對象分配后,長時間的容量增長。

內(nèi)存溢出,是針對整個應(yīng)用程序的所有對象的分配空間不足,會造成內(nèi)存溢出。

內(nèi)存泄漏

內(nèi)存泄漏指由于疏忽或錯誤造成程序未能釋放已經(jīng)不再使用的內(nèi)存的情況。內(nèi)存泄漏并非指內(nèi)存在物理上的消失,而是應(yīng)用程序分配某段內(nèi)存后,由于設(shè)

計錯誤,失去了對該段內(nèi)存的控制,因而造成了內(nèi)存的浪費。內(nèi)存泄漏與許多其他問題有著相似的癥狀,并且通常情況下只能由那些可以獲得程序源代碼的程序員才

可以分析出來。然而,有不少人習(xí)慣于把任何不需要的內(nèi)存使用的增加描述為內(nèi)存泄漏,即使嚴(yán)格意義上來說這是不準(zhǔn)確的。

一般我們常說的內(nèi)存泄漏

是指堆內(nèi)存的泄漏。堆內(nèi)存是指程序從堆中分配的,大小任意的(內(nèi)存塊的大小可以在程序運行期決定),使用完后必須顯示釋放的內(nèi)存。應(yīng)用程序一般使用

malloc,realloc,new等函數(shù)從堆中分配到一塊內(nèi)存,使用完后,程序必須負責(zé)相應(yīng)的調(diào)用free或delete釋放該內(nèi)存塊,否則,這塊內(nèi)

存就不能被再次使用,我們就說這塊內(nèi)存泄漏了。

內(nèi)存泄漏可以分為4類:

1.

常發(fā)性內(nèi)存泄漏。發(fā)生內(nèi)存泄漏的代碼會被多次執(zhí)行到,每次被執(zhí)行的時候都會導(dǎo)致一塊內(nèi)存泄漏。

2.

偶發(fā)性內(nèi)存泄漏。發(fā)生內(nèi)存泄漏的代碼只有在某些特定環(huán)境或操作過程下才會發(fā)生。常發(fā)性和偶發(fā)性是相對的。對于特定的環(huán)境,偶發(fā)性的也許就變成了常發(fā)性的。所以測試環(huán)境和測試方法對檢測內(nèi)存泄漏至關(guān)重要。

3.

一次性內(nèi)存泄漏。發(fā)生內(nèi)存泄漏的代碼只會被執(zhí)行一次,或者由于算法上的缺陷,導(dǎo)致總會有一塊僅且一塊內(nèi)存發(fā)生泄漏。比如,在類的構(gòu)造函數(shù)中分配內(nèi)存,在析構(gòu)函數(shù)中卻沒有釋放該內(nèi)存,所以內(nèi)存泄漏只會發(fā)生一次。

 

 4.

隱式內(nèi)存泄漏。程序在運行過程中不停的分配內(nèi)存,但是直到結(jié)束的時候才釋放內(nèi)存。嚴(yán)格的說這里并沒有發(fā)生內(nèi)存泄漏,因為最終程序釋放了所有申請的內(nèi)存。但

是對于一個服務(wù)器程序,需要運行幾天,幾周甚至幾個月,不及時釋放內(nèi)存也可能導(dǎo)致最終耗盡系統(tǒng)的所有內(nèi)存。所以,我們稱這類內(nèi)存泄漏為隱式內(nèi)存泄漏。

簡單點:

內(nèi)存泄漏就是忘記釋放使用完畢的內(nèi)存,讓下次使用有一定風(fēng)險。

內(nèi)存溢出就是一定的內(nèi)存空間不能裝下所有的需要存放的數(shù)據(jù),造成內(nèi)存數(shù)據(jù)溢出。

主要從以下幾部分來說明,關(guān)于內(nèi)存和內(nèi)存泄露、溢出的概念,區(qū)分內(nèi)存泄露和內(nèi)存溢出;內(nèi)存的區(qū)域劃分,了解GC回收機制;重點關(guān)注如何去監(jiān)控和發(fā)現(xiàn)內(nèi)存問題;此外分析出問題還要如何解決內(nèi)存問題。

下面就開始本篇的內(nèi)容:

第一部分 概念

眾所周知,java中的內(nèi)存由java虛擬機自己去管理的,他不像C++需要自己去釋放?;\統(tǒng)地

去講,java的內(nèi)存分配分為兩個部分,一個是數(shù)據(jù)堆,一個是棧。程序在運行的時候一般分配數(shù)據(jù)堆,把局部的臨時的變量都放進去,生命周期和進程有關(guān)系。

但是如果程序員聲明了static的變量,就直接在棧中運行的,進程銷毀了,不一定會銷毀static變量。

另外為了保證java內(nèi)存不會溢出,java中有垃圾回收機制。

System.gc()即垃圾收集機制是指jvm用于釋放那些不再使用的對象所占用的內(nèi)存。java語言并不要求jvm有g(shù)c,也沒有規(guī)定gc如何工作。垃圾收集的目的在于清除不再使用的對象。gc通過確定對象是否被活動對象引用來確定是否收集該對象。

而其中,內(nèi)存溢出就是你要求分配的java虛擬機內(nèi)存超出了系統(tǒng)能給你的,系統(tǒng)不能滿足需求,于是產(chǎn)生溢出。

內(nèi)存泄漏是指你向系統(tǒng)申請分配內(nèi)存進行使用(new),可是使用完了以后卻不歸還(delete),結(jié)果你申請到的那塊內(nèi)存你自己也不能再訪

問,該塊已分配出來的內(nèi)存也無法再使用,隨著服務(wù)器內(nèi)存的不斷消耗,而無法使用的內(nèi)存越來越多,系統(tǒng)也不能再次將它分配給需要的程序,產(chǎn)生泄露。一直下

去,程序也逐漸無內(nèi)存使用,就會溢出。

第二部分 原理

JAVA垃圾回收及對內(nèi)存區(qū)劃分

在Java虛擬機規(guī)范中,提及了如下幾種類型的內(nèi)存空間:

◇ 棧內(nèi)存(Stack):每個線程私有的。

◇ 堆內(nèi)存(Heap):所有線程公用的。

◇ 方法區(qū)(Method Area):有點像以前常說的“進程代碼段”,這里面存放了每個加載類的反射信息、類函數(shù)的代碼、編譯時常量等信息。

◇ 原生方法棧(Native Method Stack):主要用于JNI中的原生代碼,平時很少涉及。

而Java的使用的是堆內(nèi)存,java堆是一個運行時數(shù)據(jù)區(qū),類的實例(對象)從中分配空間。Java虛擬機(JVM)的堆中儲存著正在運行的應(yīng)用程序所建立的所有對象,“垃圾回收”也是主要是和堆內(nèi)存(Heap)有關(guān)。

垃圾回收的概念就是JAVA虛擬機(JVM)回收那些不再被引用的對象內(nèi)存的過程。一般我們認為正在被引用的對象狀態(tài)為“alive”,而沒有

被應(yīng)用或者取不到引用屬性的對象狀態(tài)為“dead”。垃圾回收是一個釋放處于”dead”狀態(tài)的對象的內(nèi)存的過程。而垃圾回收的規(guī)則和算法被動態(tài)的作用于

應(yīng)用運行當(dāng)中,自動回收。

JVM的垃圾回收器采用的是一種分代(generational )回收策略,用較高的頻率對年輕的對象(young

generation)進行掃描和回收,這種叫做minor collection,而對老對象(old generation)的檢查回收頻率要低很多,稱為major

collection。這樣就不需要每次GC都將內(nèi)存中所有對象都檢查一遍,這種策略有利于實時觀察和回收。

(Sun JVM 1.3

有兩種最基本的內(nèi)存收集方式:一種稱為copying或scavenge,將所有仍然生存的對象搬到另外一塊內(nèi)存后,整塊內(nèi)存就可回收。這種方法有效率,但需要有一定的空閑內(nèi)存,拷貝也有開銷。這種方法用于minor

collection。另外一種稱為mark-compact,將活著的對象標(biāo)記出來,然后搬遷到一起連成大塊的內(nèi)存,其他內(nèi)存就可以回收了。這種方法不需要占用額外的空間,但速度相對慢一些。這種方法用于major collection.

一些對象被創(chuàng)建出來只是擁有短暫的生命周期,比如 iterators 和本地變量。另外一些對象被創(chuàng)建是擁有很長的生命周期,比如持久化對象等。

垃圾回收器的分代策略是把內(nèi)存區(qū)劃分為幾個代,然后為每個代分配一到多個內(nèi)存區(qū)塊。當(dāng)其中一個代用完了分配給他的內(nèi)存后,JVM會在分配的內(nèi)存區(qū)內(nèi)執(zhí)行一個局部的GC(也可以叫minor

collection)操作,為了回收處于“dead”狀態(tài)的對象所占用的內(nèi)存。局部GC通常要比Full GC快很多。

JVM定義了兩個代,年輕代(yong generation)(有時稱為“nursery”托兒所)和老年代(old generation)。年輕代包括

“Eden space(伊甸園)”和兩個“survivor spaces”。虛擬內(nèi)存初始化的時候會把所有對象都分配到 Eden

space,并且大部分對象也會在該區(qū)域被釋放。 當(dāng)進行 minor GC的時候,VM會把剩下的沒有釋放的對象從Eden space移動到其中一個survivor

spaces當(dāng)中。此外,VM也會把那些長期存活在survivor spaces 里的對象移動到 老生代的“tenured” space中。當(dāng) tenured

generation 被填滿后,就會產(chǎn)生Full GC,F(xiàn)ull GC會相對比較慢因為回收的內(nèi)容包括了所有的 live狀態(tài)的對象。pemanet

generation這個代包括了所有java虛擬機自身使用的相對比較穩(wěn)定的數(shù)據(jù)對象,比如類和對象方法等。

關(guān)于代的劃分,可以從下圖中獲得一個概況:

第三部分 總結(jié)

內(nèi)存溢出主要是由于代碼編寫時對某些方法、類應(yīng)用不合理,或者沒有預(yù)估到臨時對象會占用很大內(nèi)存量,或者把過多的數(shù)據(jù)放入JVM緩存,或者性能

壓力大導(dǎo)致消息堆積而占用內(nèi)存,以至于在性能測試時,生成龐大數(shù)量的臨時對象,GC時沒有做出有效回收甚至根本就不能回收,造成內(nèi)存空間不足,內(nèi)存溢出。

如果編碼之前,對內(nèi)存使用量進行預(yù)估,對放在內(nèi)存中的數(shù)據(jù)進行評估,保證有用的信息盡快釋放,無用的信息能夠被GC回收,這樣在一定程度上是可以避免內(nèi)存溢出問題的。

怎樣解決Java中內(nèi)存泄露

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

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

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

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

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

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

java存在內(nèi)存泄露嗎?怎樣模擬java的內(nèi)存泄露?

java也是存在內(nèi)存泄漏的,雖然jvm會自動回收沒有引用的對象,但是編碼過程中寫出不正確的代碼一樣會出現(xiàn)內(nèi)存泄漏

比如下面的代碼

public?class?Test?{

public?static?void?main(String[]?args)?{

Test?test?=?new?Test();

test.thread.start();

test?=?null;

}

public?Thread?thread?=?new?Thread(){

public?void?run()?{

while(true){

}

};

};

}

上面的程序中new了一個對象test,并且啟動了test的成員是一個線程,并且是死循環(huán),后面把test設(shè)為null,現(xiàn)在感覺好像是沒有引用了,但是實際上,test對象仍然在內(nèi)存中,

我們知道內(nèi)部類是會持有外部類的引用的,這里的thread也相當(dāng)于一個內(nèi)部類,所以這里的thread持有test的引用,這就導(dǎo)致gc并不會回收test,這里的test沒有占多少內(nèi)存,

但是在一些大一點的對象(比如android里的activity)被泄漏時對程序影響就比較大了

新聞標(biāo)題:java代碼內(nèi)存泄露 java中的內(nèi)存泄露是怎么回事
網(wǎng)頁鏈接:http://muchs.cn/article38/dospepp.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供、響應(yīng)式網(wǎng)站、電子商務(wù)、關(guān)鍵詞優(yōu)化定制網(wǎng)站、定制開發(fā)

廣告

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

網(wǎng)站托管運營