A. java內存泄漏怎麼解決
一般情況下內存泄漏的避免
在不涉及復雜數據結構茄哪的一般情況下,Java 的內存泄露表現為一個內存對象的生命周期超出了程序需要它的時間長度。我們有時也將其稱為「對象游離」。
例如:
public class FileSearch{ private byte [] content; private File mFile; public FileSearch(File file){ mFile = file; } public boolean hasString(String str){ int size = getFileSize(mFile); content = new byte [size]; loadFile(mFile, content); String s = new String(content); return s.contains(str); }}
在這段代碼中,FileSearch 類中有一個函數 hasString ,用來判斷文檔中是否含有指定的字元串。流程是先將mFile 載入到內存中,然後進行判斷。但是,這里的問題是,將 content 聲明為了實例變數,而不是本地變數。於是,在此函數返回之後,內存中仍然存在整個文件的數據。而很明顯,這些數據我們後續是不再需要的,這就造成了內存的無故浪費。
要避免這種情況下的內存泄露,要求我們以C/C++ 的內存管理思維來管理自己分配的內存。第一,是在聲明對象引用之前,明確內存對象的有效作用域。在一個函數內有效的內存對象,應該聲明為 local 變數,與類實例生命周期相同的要聲明為實例變數……以此類推。第二,在內存對象不再需要時,記得手動將其引用置空。
復雜數據結構中的內存泄露問題
在實際的項目中,我們經常用到一些較為復雜的數據結構用於緩存程序運行過程中需要的數據信息。有時,由於數據結構過於復雜,或者我們存在一些特殊的需求(例如,在內存允許的情況下,盡可能多的緩存信息來提高程序的運行速度等情況),我們很難對數據結構中數據的生命周期作出明確的界定。這個時候,我們可以使用Java 中一種特殊的機制來達到防止內存泄露的目的。
之前我們介紹過,Java 的 GC 機制是建立在跟蹤內存的引用機制上的。而在此之前,我們所使用的引用都只是定義一個「 Object o; 」這樣形式的。事實上,這只是 Java 引用機制中的一種默認情況,除此之外,還有其他的一些引用方式。通過使用這些特殊的引用機制,配合 GC 機制,就可以達到一些我改仔們需要的效果。
Java中的幾種引用方式
Java中有幾種不同的引用方式,它們分別是:強引用、軟引用、弱引用和虛引用。下面,我們首先詳細地了解下這幾種引用方式的意義。
強引用
在此之前我們介紹的內容中所使用的引用 都是強引用,這是使用最普遍的引用。如果一個對象具有強引用,那就類似於必不可少的生活用品,垃圾回收器絕不會回收它。當內存空 間不足,Java 虛擬機寧願拋出 OutOfMemoryError 錯誤,使程序異常終止,也不會靠隨核納汪意回收具有強引用的對象來解決內存不足問題。
軟引用(SoftReference )
SoftReference 類的一個典型用途就是用於內存敏感的高速緩存。SoftReference 的原理是:在保持對對象的引用時保證在 JVM 報告內存不足情況之前將清除所有的軟引用。關鍵之處在於,垃圾收集器在運行時可能會(也可能不會)釋放軟可及對象。對象是否被釋放取決於垃圾收集器的演算法 以及垃圾收集器運行時可用的內存數量。
弱引用(WeakReference )
WeakReference 類的一個典型用途就是規范化映射( canonicalized mapping )。另外,對於那些生存期相對較長而且重新創建的開銷也不高的對象來說,弱引用也比較有用。關鍵之處在於,垃圾收集器運行時如果碰到了弱可及對象,將釋放 WeakReference 引用的對象。然而,請注意,垃圾收集器可能要運行多次才能找到並釋放弱可及對象。
虛引用(PhantomReference )
PhantomReference 類只能用於跟蹤對被引用對象即將進行的收集。同樣,它還能用於執行 pre-mortem 清除操作。PhantomReference 必須與 ReferenceQueue 類一起使用。需要 ReferenceQueue 是因為它能夠充當通知機制。當垃圾收集器確定了某個對象是虛可及對象時, PhantomReference 對象就被放在它的 ReferenceQueue 上。將 PhantomReference 對象放在 ReferenceQueue 上也就是一個通知,表明 PhantomReference 對象引用的對象已經結束,可供收集了。這使您能夠剛好在對象佔用的內存被回收之前採取行動。Reference與 ReferenceQueue 的配合使用。
GC、 Reference 與 ReferenceQueue 的交互
A、 GC無法刪除存在強引用的對象的內存。
B、 GC發現一個只有軟引用的對象內存,那麼:
① SoftReference對象的 referent 域被設置為 null ,從而使該對象不再引用 heap 對象。
② SoftReference引用過的 heap 對象被聲明為 finalizable 。
③ 當 heap 對象的 finalize() 方法被運行而且該對象佔用的內存被釋放, SoftReference 對象就被添加到它的 ReferenceQueue (如果後者存在的話)。
C、 GC發現一個只有弱引用的對象內存,那麼:
① WeakReference對象的 referent 域被設置為 null , 從而使該對象不再引用heap 對象。
② WeakReference引用過的 heap 對象被聲明為 finalizable 。
③ 當heap 對象的 finalize() 方法被運行而且該對象佔用的內存被釋放時, WeakReference 對象就被添加到它的 ReferenceQueue (如果後者存在的話)。
D、 GC發現一個只有虛引用的對象內存,那麼:
① PhantomReference引用過的 heap 對象被聲明為 finalizable 。
② PhantomReference在堆對象被釋放之前就被添加到它的 ReferenceQueue 。
值得注意的地方有以下幾點:
1、 GC 在一般情況下不會發現軟引用的內存對象,只有在內存明顯不足的時候才會發現並釋放軟引用對象的內存。
2、 GC 對弱引用的發現和釋放也不是立即的,有時需要重復幾次 GC ,才會發現並釋放弱引用的內存對象。3、軟引用和弱引用在添加到 ReferenceQueue 的時候,其指向真實內存的引用已經被置為空了,相關的內存也已經被釋放掉了。而虛引用在添加到 ReferenceQueue 的時候,內存還沒有釋放,仍然可以對其進行訪問。
代碼示例
通過以上的介紹,相信您對Java 的引用機制以及幾種引用方式的異同已經有了一定了解。光是概念,可能過於抽象,下面我們通過一個例子來演示如何在代碼中使用 Reference 機制。
String str = new String( " hello " ); // ①ReferenceQueue < String > rq = new ReferenceQueue < String > (); // ②WeakReference < String > wf = new WeakReference < String > (str, rq); // ③str = null ; // ④取消"hello"對象的強引用String str1 = wf.get(); // ⑤假如"hello"對象沒有被回收,str1引用"hello"對象// 假如"hello"對象沒有被回收,rq.poll()返回nullReference <? extends String > ref = rq.poll(); // ⑥
在以上代碼中,注意⑤⑥兩處地方。假如「hello 」對象沒有被回收 wf.get() 將返回「 hello 」字元串對象, rq.poll() 返回 null ;而加入「 hello 」對象已經被回收了,那麼 wf.get() 返回 null , rq.poll() 返回 Reference 對象,但是此 Reference 對象中已經沒有 str 對象的引用了 ( PhantomReference 則與WeakReference 、 SoftReference 不同 )。
引用機制與復雜數據結構的聯合應用
了解了GC 機制、引用機制,並配合上 ReferenceQueue ,我們就可以實現一些防止內存溢出的復雜數據類型。
例如,SoftReference 具有構建 Cache 系統的特質,因此我們可以結合哈希表實現一個簡單的緩存系統。這樣既能保證能夠盡可能多的緩存信息,又可以保證 Java 虛擬機不會因為內存泄露而拋出 OutOfMemoryError 。這種緩存機制特別適合於內存對象生命周期長,且生成內存對象的耗時比較長的情況,例如緩存列表封面圖片等。對於一些生命周期較長,但是生成內存對象開銷不大的情況,使用WeakReference 能夠達到更好的內存管理的效果。
附SoftHashmap 的源碼一份,相信看過之後,大家會對 Reference 機制的應用有更深入的理解。