JAVA內存泄露

一、問題的提出

Java的一個重要優點就是通過垃圾收集器(Garbage CollectionGC)自動管理內存的回收,程序員不需要通過調用函數來釋放內存。因此,很多程序員認爲Java不存在內存泄漏問題,或者認爲即使有內存泄漏也不是程序的責任,而是GCJVM的問題。其實,這種想法是不正確的,因爲Java也存在內存泄露,但它的表現與C++不同。

隨着越來越多的服務器程序採用Java技術,例如JSPServlet EJB等,服務器程序往往長期運行。另外,在很多嵌入式系統中,內存的總量非常有限。內存泄露問題也就變得十分關鍵,即使每次運行少量泄漏,長期運行之後,系統也是面臨崩潰的危險。

二、Java是如何管理內存

爲了判斷Java中是否有內存泄露,我們首先必須瞭解Java是如何管理內存的。Java的內存管理就是對象的分配和釋放問題。分配內存的方式多種多樣,取決於該種語言的語法結構。但不論是哪一種語言的內存分配方式,最後都要返回所分配的內存塊的起始地址,即返回一個指針到內存塊的首地址。在Java中,程序員需要通過關鍵字new爲每個對象申請內存空間 (基本類型除外),所有的對象都在堆 (Heap)中分配空間。另外,對象的釋放是由GC決定和執行的。在Java中,內存的分配是由程序完成的,而內存的釋放是有GC完成的,這種收支兩條線的方法確實簡化了程序員的工作。但同時,它也加重了JVM的工作。這也是Java程序運行速度較慢的原因之一。

因爲,GC爲了能夠正確釋放對象,GC必須監控每一個對象的運行狀態,包括對象的申請、引用、被引用、賦值等,GC都需要進行監控。

監視對象狀態是爲了更加準確地、及時地釋放對象,而釋放對象的根本原則就是該對象不再被引用。

爲了更好理解GC的工作原理,我們可以將對象考慮爲有向圖的頂點,將引用關係考慮爲圖的有向邊,有向邊從引用者指向被引對象。另外,每個線程對象可以作爲一個圖的起始頂點,例如大多程序從main進程開始執行,那麼該圖就是以main進程頂點開始的一棵根樹。在這個有向圖中,根頂點可達的對象都是有效對象,GC將不回收這些對象。如果某個對象 (連通子圖)與這個根頂點不可達(注意,該圖爲有向圖),那麼我們認爲這個(這些)對象不再被引用,可以被GC回收。

以下,我們舉一個例子說明如何用有向圖表示內存管理。對於程序的每一個時刻,我們都有一個有向圖表示JVM的內存分配情況。以下右圖,就是左邊程序運行到第6行的示意圖。

  

Java使用有向圖的方式進行內存管理,可以消除引用循環的問題,例如有三個對象,相互引用,只要它們和根進程不可達的,那麼GC也是可以回收它們的。這種方式的優點是管理內存的精度很高,但是效率較低。另外一種常用的內存管理技術是使用計數器,例如COM模型採用計數器方式管理構件,它與有向圖相比,精度行低(很難處理循環引用的問題),但執行效率很高。

 Java 中,所有對象都駐留在堆內存,因此局部對象就不存在。當你創建一個對象時,Java 虛擬機(JVM)爲該對象分配內存、調用構造器並開始跟蹤你使用的對象。當你停止使用一個對象(就是說,當沒有對該對象有效的引用時),JVM 通過垃圾回收器將該對象標記爲釋放狀態。當垃圾回收器要釋放一個對象的內存時,它首先調用該對象的finalize() 方法(如果該對象定義了此方法的話)。垃圾回收器以獨立的低優先級的方式運行,所以只有當其他線程都掛起等待內存釋放的情況出現時,它纔開始釋放對象的內存。

三、java垃圾收集器

垃圾收集器線程是一種低優先級的線程,在一個Java程序的生命週期中,它只有在內存空閒的時候纔有機會運行。

垃圾收集器的特點和它的執行機制:

垃圾收集器系統有自己的一套方案來判斷哪個內存塊是應該被回收的,哪個是不符合要求暫不回收的。垃圾收集器在一個Java程序中的執行是自動的,不能強制執行,即使程序員能明確地判斷出有一塊內存已經無用了,是應該回收的,程序員也不能強制垃圾收集器回收該內存塊。程序員唯一能做的就是通過調用System. gc 方法來"建議"執行垃圾收集器,但其是否可以執行,什麼時候執行卻都是不可知的。這也是垃圾收集器的最主要的缺點。當然相對於它給程序員帶來的巨大方便性而言,這個缺點是瑕不掩瑜的。

垃圾收集器的工作是發現應用程序不再需要的對象,並在這些對象不再被訪問或引用時將它們刪除。從根節點(在java應用程序的整個生存週期內始終存在的那些類)開始,遍歷被應用的所有節點進行清除。在它遍歷這些節點的同時,它跟蹤哪些對象當前正被引用着。任何類只要不再被引用,它就符合垃圾收集的條件。當刪除這些對象後,就將它們所佔用的內存資源返回給jvm

1)、垃圾收集器的主要特點:

1.垃圾收集器的工作目標是回收已經無用的對象的內存空間,從而避免內存滲漏體的產生,節省內存資源,避免程序代碼的崩潰。

2.垃圾收集器判斷一個對象的內存空間是否無用的標準是:如果該對象不能再被程序中任何一個"活動的部分"所引用,此時我們就說,該對象的內存空間已經無用。所謂"活動的部分",是指程序中某部分參與程序的調用,正在執行過程中,尚未執行完畢。

3.垃圾收集器線程雖然是作爲低優先級的線程運行,但在系統可用內存量過低的時候,它可能會突發地執行來挽救內存資源。當然其執行與否也是不可預知的。

4.垃圾收集器不可以被強制執行,但程序員可以通過調用System. gc方法來建議執行垃圾收集器。

5.不能保證一個無用的對象一定會被垃圾收集器收集,也不能保證垃圾收集器在一段Java語言代碼中一定會執行。因此在程序執行過程中被分配出去的內存空間可能會一直保留到該程序執行完畢,除非該空間被重新分配或被其他方法回收。由此可見,完全徹底地根絕內存滲漏體的產生也是不可能的。但是請不要忘記,Java的垃圾收集器畢竟使程序員從手工回收內存空間的繁重工作中解脫了出來。設想一個程序員要用CC++來編寫一段10萬行語句的代碼,那麼他一定會充分體會到Java的垃圾收集器的優點!

6.同樣沒有辦法預知在一組均符合垃圾收集器收集標準的對象中,哪一個會被首先收集。

7.循環引用對象不會影響其被垃圾收集器收集。

8.可以通過將對象的引用變量(reference variables,即句柄handles)初始化爲null值,來暗示垃圾收集器來收集該對象。但此時,如果該對象連接有事件監聽器(典型的 AWT組件),那它還是不可以被收集。所以在設一個引用變量爲null值之前,應注意該引用變量指向的對象是否被監聽,若有,要首先除去監聽器,然後纔可以賦空值。

9.每一個對象都有一個finalize( )方法,這個方法是從Object類繼承來的。

10finalize( )方法用來回收內存以外的系統資源,就像是文件處理器和網絡連接器。該方法的調用順序和用來調用該方法的對象的創建順序是無關的。換句話說,書寫程序時該方法的順序和方法的實際調用順序是不相干的。請注意這只是finalize( )方法的特點。

11.每個對象只能調用finalize( )方法一次。如果在finalize( )方法執行時產生異常(exception),則該對象仍可以被垃圾收集器收集。

12.垃圾收集器跟蹤每一個對象,收集那些不可到達的對象(即該對象沒有被程序的任何"活的部分"所調用),回收其佔有的內存空間。但在進行垃圾收集的時候,垃圾收集器會調用finalize( )方法,通過讓其他對象知道它的存在,而使不可到達的對象再次"復甦"爲可到達的對象。既然每個對象只能調用一次finalize( )方法,所以每個對象也只可能"復甦"一次。

13finalize( )方法可以明確地被調用,但它卻不能進行垃圾收集。

14finalize( )方法可以被重載(overload),但只有具備初始的finalize( )方法特點的方法纔可以被垃圾收集器調用。

15.子類的finalize( )方法可以明確地調用父類的finalize( )方法,作爲該子類對象的最後一次適當的操作。但Java編譯器卻不認爲這是一次覆蓋操作(overriding),所以也不會對其調用進行檢查。

16.當finalize( )方法尚未被調用時,System. runFinalization( )方法可以用來調用finalize( )方法,並實現相同的效果,對無用對象進行垃圾收集。

此時容易引起一個誤解——大家可能想finalize() 方法是安全的,這時一些重要的事情需要注意:

首先,只有當垃圾回收器釋放該對象的內存時,纔會執行finalize()。如果在 Applet 或應用程序退出之前垃圾回收器沒有釋放內存,垃圾回收器將不會調用finalize()

其次,除非垃圾回收器認爲Applet或應用程序需要額外的內存,否則它不會試圖釋放不再使用的對象的內存。換句話說,有可能出現這樣的情況:一個 Applet 給少量的對象分配了內存,但沒有造成嚴重的內存需求,於是垃圾回收器沒有釋放這些對象的內存程序就退出了。

顯然,如果爲某個對象定義了finalize() 方法,JVM可能不會調用它,因爲垃圾回收器不曾釋放過那些對象的內存。即使調用System.gc() 也可能不會起作用,因爲它僅僅是給 JVM 的一個建議而不是命令,所以finalize()方法的作用也就不是那麼明顯。Java 1.1中有一個System.runFinalizersOnExit()方法部分地解決了這個問題。(不要將這個方法與 Java1.0中的System.runFinalizations()方法相混淆。)不象System.gc() 方法那樣,System.runFinalizersOnExit()方法並不立即試圖啓動垃圾回收器。而是當應用程序或 Applet 退出時,它調用每個對象的finalize()方法。

結論:不應當依靠垃圾回收器或finalize() 來執行你的 Applet 和應用程序的資源清除工作。取而代之,應當使用確定的方法來清除那些資源或創建一個try...finally 塊(或類似的機制)來實現。

17.當一個方法執行完畢,其中的局部變量就會超出使用範圍,此時可以被當作垃圾收集,但以後每當該方法再次被調用時,其中的局部變量便會被重新創建。

18Java語言使用了一種"標記交換區的垃圾收集算法"。該算法會遍歷程序中每一個對象的句柄,爲被引用的對象做標記,然後回收尚未做標記的對象。所謂遍歷可以簡單地理解爲"檢查每一個"

19Java語言允許程序員爲任何方法添加finalize( )方法,該方法會在垃圾收集器交換回收對象之前被調用。但不要過分依賴該方法對系統資源進行回收和再利用,因爲該方法調用後的執行結果是不可預知的。

通過以上對垃圾收集器特點的瞭解,你應該可以明確垃圾收集器的作用,和垃圾收集器判斷一塊內存空間是否無用的標準。簡單地說,當你爲一個對象賦值爲null並且重新定向了該對象的引用者,此時該對象就符合垃圾收集器的收集標準。

典型地,GC不會自動執行,直到程序需要的內存比當前可用內存多時才調用,此時,jvm將首先嚐試激活GC以得到更多的可用內存,如果仍得不到充足的可用內存,jvm將轉向從操作系統申請更多的內存,直到最終超過分配的最大內存而導致java.lang.OutOfMemoryError

四、什麼是Java中的內存泄露

1)、java內存泄漏

Java中,內存泄漏就是存在一些被分配的對象,這些對象有下面兩個特點,首先,這些對象是可達的,即在有向圖中,存在通路可以與其相連;其次,這些對象是無用的,即程序以後不會再使用這些對象。如果對象滿足這兩個條件,這些對象就可以判定爲Java中的內存泄漏,這些對象不會被GC所回收,然而它卻佔用內存。

C++中,內存泄漏的範圍更大一些。有些對象被分配了內存空間,然後卻不可達,由於C++中沒有GC,這些內存將永遠收不回來。在Java中,這些不可達的對象都由GC負責回收,因此程序員不需要考慮這部分的內存泄露。

通過分析,我們得知,對於C++,程序員需要自己管理邊和頂點,而對於Java程序員只需要管理邊就可以了(不需要管理頂點的釋放)。通過這種方式,Java提高了編程的效率。

 

因此,通過以上分析,我們知道在Java中也有內存泄漏,但範圍比C++要小一些。因爲Java從語言上保證,任何對象都是可達的,所有的不可達對象都由GC管理。

對於程序員來說,GC基本是透明的,不可見的。雖然,我們只有幾個函數可以訪問GC,例如運行GC的函數System.gc(),但是根據Java語言規範定義, 該函數不保證JVM的垃圾收集器一定會執行。因爲,不同的JVM實現者可能使用不同的算法管理GC。通常,GC的線程的優先級別較低。JVM調用GC的策略也有很多種,有的是內存使用到達一定程度時,GC纔開始工作,也有定時執行的,有的是平緩執行GC,有的是中斷式執行GC。但通常來說,我們不需要關心這些。除非在一些特定的場合,GC的執行影響應用程序的性能,例如對於基於Web的實時系統,如網絡遊戲等,用戶不希望GC突然中斷應用程序執行而進行垃圾回收,那麼我們需要調整GC的參數,讓GC能夠通過平緩的方式釋放內存,例如將垃圾回收分解爲一系列的小步驟執行,Sun提供的HotSpot JVM就支持這一特性。

下面給出了一個簡單的內存泄露的例子。在這個例子中,我們循環申請Object對象,並將所申請的對象放入一個Vector中,如果我們僅僅釋放引用本身,那麼Vector仍然引用該對象,所以這個對象對GC來說是不可回收的。因此,如果對象加入到Vector後,還必須從Vector中刪除,最簡單的方法就是將Vector對象設置爲null

Vector v=new Vector(10);

for (int I=1;I<100; I++)

{

Object o=new Object();

v.add(o);

o=null;

}

//此時,所有的Object對象都沒有被釋放,因爲變量v引用這些對象。

實際上無用,而還被引用的對象,gc 就無能爲力了(事實上gc認爲它還有用),這一點是導致內存泄露最重要的原因。

2)、判斷一個對象是否符合垃圾收集器的收集標準

Object obj = new Object ( ) ;

我們知道,objObject的一個句柄。當出現new關鍵字時,就給新建的對象分配內存空間,而obj的值就是新分配的內存空間的首地址,即該對象的值(對象的值和對象的內容是不同含義的兩個概念:對象的值就是指其內存塊的首地址,即對象的句柄;而對象的內容則是其具體的內存塊)。此時如果有 obj = null obj指向的內存塊此時就無用了,因爲下面再沒有調用該變量了。

請看以下三種內存管理:

 

程序段1

1fobj = new Object ( ) ;

2fobj. Method ( ) ;

3fobj = new Object ( ) ;

4fobj. Method ( ) ;

問:這段代碼中,第幾行的fobj 符合垃圾收集器的收集標準?

答:第3行。因爲第3行的fobj被賦了新值,產生了一個新的對象,即換了一塊新的內存空間,也相當於爲第1行中的fobj賦了null值。

程序段2

1Object sobj = new Object ( ) ;

2Object sobj = null ;

3Object sobj = new Object ( ) ;

4sobj = new Object ( ) ;

問:這段代碼中,第幾行的內存空間符合垃圾收集器的收集標準?

答:第1行和第3行。因爲第2行爲sobj賦值爲null,所以在此第1行的sobj符合垃圾收集器的收集標準。而第4行相當於爲sobj賦值爲null,所以在此第3行的sobj也符合垃圾收集器的收集標準。

如果有一個對象的句柄a,且你把a作爲某個構造器的參數,即 new Constructor ( a )的時候,即使你給a賦值爲nulla也不符合垃圾收集器的收集標準。直到由上面構造器構造的新對象被賦空值時,a纔可以被垃圾收集器收集。

程序段3

1Object aobj = new Object ( ) ;

2Object bobj = new Object ( ) ;

3Object cobj = new Object ( ) ;

4aobj = bobj;

5aobj = cobj;

6cobj = null;

7aobj = null;

問:這段代碼中,第幾行的內存空間符合垃圾收集器的收集標準?

答:自己做,答案明天放在論壇中。

答:第7行。注意這類題型是認證考試中可能遇到的最難題型了。

1-3分別創建了Object類的三個對象:aobjbobjcobj

4:此時對象aobj的句柄指向bobj,所以該行的執行不能使aobj符合垃圾收集器的收集標準。

5:此時對象aobj的句柄指向cobj,所以該行的執行不能使aobj符合垃圾收集器的收集標準。

6:此時仍沒有任何一個對象符合垃圾收集器的收集標準。

7:對象cobj符合了垃圾收集器的收集標準,因爲cobj的句柄指向單一的地址空間。在第6行的時候,cobj已經被賦值爲null,但由cobj同時還指向了aobj(第5行),所以此時cobj並不符合垃圾收集器的收集標準。而在第7行,aobj所指向的地址空間也被賦予了空值null,這就說明了,由cobj所指向的地址空間已經被完全地賦予了空值。所以此時cobj最終符合了垃圾收集器的收集標準。 但對於aobjbobj,仍然無法判斷其是否符合收集標準。

 

總之,在Java語言中,判斷一塊內存空間是否符合垃圾收集器收集標準的標準只有兩個:

1.給對象賦予了空值null,以下再沒有調用過。

2.給對象賦予了新值,既重新分配了內存空間。

最後,一塊內存空間符合了垃圾收集器的收集標準,並不意味着這塊內存空間就一定會被垃圾收集器收集

五、java內存管理示例

1)、多重引用

程序中的對象是否有用和java gc認爲對象是否有用是有差別的:

1       程序員編寫代碼通常是認爲被創建的對象在其生命週期結束後無用

2       gc認爲只有對象的引用記數=0的時候,該對象纔是無用的。

兩者會產生不一致的地方,如下圖所示:

 

代碼(程序員的觀點)                          gc(java)

Public void fun1(){

……

//創建局部變量E

Object E = new E();                             E.count ++

A.a = E;                                      E.count ++

B.b = E;                                      E.count ++

C.c = E;                                      E.count ++

D.d = E;                                      E.count ++

 

//我們認爲

//E沒用了,釋放E

E = null;                                      E.count –

……

}

認爲已無用                                    E的引用數=4

                                                                   仍舊有用

應該釋放                                                   gc不負責釋放

 

結論:

1       如果要釋放對象,就必須使其的引用記數爲0,只有那些不再被引用的對象才能被釋放,這個原理很簡單,但是很重要,是導致內存泄露的基本原因,也是解決內存泄露方法的宗旨。

2       程序員無須管理對象空間具體的分配和釋放過程,但必須要關注被釋放對象的引用記數是否爲0

3       一個對象可能被其他對象引用的過程的幾種

a、直接賦值,如上例中的A.a = E;

b、通過參數傳遞,例如public void addObject(Object E)

c、其它一些情況如系統調用等。

2)、幾種容易遺忘並導致不能釋放的引用情況

1、通常的無用引

上面說明了在java應用程序執行期間具有不同生存週期的兩個類,類A首先被實例化,並會在很長一段時間或程序的整個生存週期內存在,在某個時候,類B被創建,類A添加對這個新創建的類的一個引用。現在,我們假定類B是某個用戶界面小部件,它由用戶顯示甚至解除。如果沒清除類AB的引用,則即便不再需要類B,並且在執行下一個垃圾收集週期以後,類B仍將存在並佔用內存空間

2、內部類的引用

內部類的引用是比較容易遺忘的一種,而且一旦沒釋放可能導致一系列的後繼類對象沒有釋放,從內部類的引用來看我們要釋放對象A,需要做到的不僅是將對象A的引用記數清爲0,最好是將指向A對象以及A對象內部成員的引用都清爲0

3、監聽器引用

java編程中,我們都需要和監聽器打交道,通常一個應用當中會用到很多監聽器,我們會調用一個控件的諸如AddXXXListener()等方法來增加監聽器,建議在釋放對象的時候刪除這些對象,如果不這樣做,那麼程序存在內存泄露的機會將增大很多。

4、外部模塊的引用

對於程序員而言,自己的程序很清楚,如果發現內存泄露,自己對這些對象的引用可以很快定位並解決,但是現在的應用軟件並非一個人實現,模塊化的思想在現代軟件中非常明顯,所以程序員要小心外部模塊不經意的引用,例如程序員A負責A模塊,它調用了B模塊的一個方法如:

public void registerMsg(Object b);

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

5、系統的引用

這種引用比較少,但很難定位和解決,類似監聽器引用,當創建系統類對象,譬如線程、定時器、面板、顏色選擇框、文件選擇對話框等,可能會產生一些引用,導致和它們相關的類不能釋放,對於其中的原因和解決方案,大家可以去研究。

3)、順序和效率

當發現有很多類對象沒有被釋放的時候,不要急於解決這些問題,要分析這些類對象的關係,舉例如下:

如果只要釋放b圈內的對象,那麼最好從A1開始釋放,首先關注a1的釋放情況,如果要釋放c圈內的對象,那麼最好從A對象釋放開始做起,這和java GC的工作機制一樣,就可以提高我們解決內存泄露問題的效率。

代碼修改的原則:

如果要釋放一個對象,就要將指向該對象的引用清空,最好連帶指向該對象內部的引用也清空。例如,要釋放A4對象,就要釋放A1->A4的應用A2->A4的引用

4)、經驗總結、預防措施和規範建議

1、容器的removeall()方法

當類從JpanelJdialog或其它容器類繼承的時候,刪除該對象之前不妨調用它的removeall()方法

2、線程的interrupe()方法

當類對象是一個Thread的時候,刪除該對象之前不妨調用它的interrupe()方法

3JfileChooserremoveChoosableFileFilter()

如果創建了一個JfileChooser,並且加入了自己的文件過濾器,刪除該對象之前不妨調用它的removeChoosableFileFilter()方法

4、調用TimerTimerTaskCancel()方法

5、當不需要一個類時,最好刪除它的監聽器

6、內存檢測過程中不僅要關注自己編寫的類對象,同時也要關注一些基本類型的對象,例如:int[],String,char[]等等。

7、在確認一個對象無用後,將其所有引用顯式的置爲null

六、如何檢測內存泄漏

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

下面,我們將簡單介紹Optimizeit的基本功能和工作原理。

Optimizeit Profiler版本4.11支持ApplicationAppletServletRomote Application四類應用,並且可以支持大多數類型的JVM,包括SUN JDK系列,IBMJDK系列,和JbuilderJVM等。並且,該軟件是由Java編寫,因此它支持多種操作系統。Optimizeit系列還包括Thread DebuggerCode Coverage兩個工具,分別用於監測運行時的線程狀態和代碼覆蓋面。

當設置好所有的參數了,我們就可以在OptimizeIt環境下運行被測程序,在程序運行過程中,Optimizeit可以監視內存的使用曲線(如下圖),包括JVM申請的堆(heap)的大小,和實際使用的內存大小。另外,在運行過程中,我們可以隨時暫停程序的運行,甚至強行調用GC,讓GC進行內存回收。通過內存使用曲線,我們可以整體瞭解程序使用內存的情況。這種監測對於長期運行的應用程序非常有必要,也很容易發現內存泄露。

在運行過程中,我們還可以從不同視角觀查內存的使用情況,Optimizeit提供了四種方式:

    堆視角。 這是一個全面的視角,我們可以瞭解堆中的所有的對象信息(數量和種類),並進行統計、排序,過濾。瞭解相關對象的變化情況。

    方法視角。通過方法視角,我們可以得知每一種類的對象,都分配在哪些方法中,以及它們的數量。

    對象視角。給定一個對象,通過對象視角,我們可以顯示它的所有出引用和入引用對象,我們可以瞭解這個對象的所有引用關係。

    引用圖。 給定一個根,通過引用圖,我們可以顯示從該頂點出發的所有出引用。

在運行過程中,我們可以隨時觀察內存的使用情況,通過這種方式,我們可以很快找到那些長期不被釋放,並且不再使用的對象。我們通過檢查這些對象的生存週期,確認其是否爲內存泄露。在實踐當中,尋找內存泄露是一件非常麻煩的事情,它需要程序員對整個程序的代碼比較清楚,並且需要豐富的調試經驗,但是這個過程對於很多關鍵的Java程序都是十分重要的。

綜上所述,Java也存在內存泄露問題,其原因主要是一些對象雖然不再被使用,但它們仍然被引用。爲了解決這些問題,我們可以通過軟件工具來檢查內存泄露,檢查的主要原理就是暴露出所有堆中的對象,讓程序員尋找那些無用但仍被引用的對象。

發佈了39 篇原創文章 · 獲贊 2 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章