1.Java垃圾回收器概述
程序員需要通過關鍵字new創建java對象,即可視為java對象申請內存空間,JVM會再堆內存中為每個對象分配空間,當一個java對象失去引用時JVM的垃圾回收機制會自動清除它們,并回收它們所占用的內存空間。
對于JVM垃圾回收機制來說,是否回收一個對象的標準在于:是否還有引用變量引用該對象?
也就是說,當java對象被創建出來后,GC就會實時的地監控每一個對象的運行狀態,包括對象的申請,引用,被引用,賦值等。當其監控到某個對象不再被引用變量所引用時,立即GC就會回收它所占用的空間。
基本上,可以把JVM內存中對象引用理解成一種有向圖,把引用變量,對象都當成為有向圖的頂點,將引用關系當成圖的有向邊,有向邊總是從引用端指向被引用的java對象。GC是采用有向圖的方式來管理內存中的對象,因此可以方便地循環引用問題。
當一個對象在堆內存中運行時,根據他在對應有向圖的狀態,可以把它所處的狀態分為以下3種:
a.可達狀態:當一個對象被創建后,有一個以上的引用變量引用它。在有向圖中可以從起始頂點導航帶該對象,那它就處于可達狀態,程序可以通過引用變量來調用
b.可恢復狀態:如果程序中某個對象不再有任何引用變量引用它,它將先進圖可恢復狀態,此時GC準備回收該對象所占用的內存。在回收之前,系統會調用可恢復狀態的對象的finalize方法進行資源清理,如果重新讓一個以上引用變量引用該對象,則這個對象會變為可達狀態,否則進入不可達狀態
c.不可達狀態:當對象的所有關聯都被切斷,且系統調用的finalize方法依然沒有使該對象變為可達狀態,那這個對象將永久性的失去引用,最后變為不可達狀態。只有一個對象處于不可達狀態時,系統才會真正的回收該對象所占有的資源。
1.Java中對象引用分類 為了更好的管理對象的引用,Java2平臺里面引入了java.lang.ref包,這個包中的類可以讓我們引用對象,而使得這些對象不用停留在內存中。不僅僅如此,這個包提供了3個類SoftReference, WeakReference和 PhantomReference,它們分別代表了系統對對象的3鐘引用方式,軟引用,弱引用和虛引用。歸納起來,java語言對對象的引用有如下4鐘:強引用,軟引用,弱引用和虛引用。
Java中的對象引用主要有以下幾種類型:
1)強可及對象(strongly reachable):
可以通過強引用訪問的對象,一般來說,我們平時寫代碼的方式都是使用的強引用對象,比如下邊的代碼段:
StringBuilder builder= new StringBuilder();
上邊代碼部分引用obj這個引用將引用內存堆中的一個對象,這種情況下,只要obj的引用存在,垃圾回收器就永遠不會釋放該對象的存儲空間。這種對象我們又成為強引用(Strong references),這種強引用方式就是Java語言的原生的Java引用,我們幾乎每天編程的時候都用到。上邊代碼JVM存儲了一個StringBuilder類型的對象的強引用在變量builder呢。強引用和GC的交互是這樣的,如果一個對象通過強引用可達或者通過強引用鏈可達的話這種對象就成為強可及對象,這種情況下的對象垃圾回收器不予理睬。如果我們開發過程不需要垃圾回器回收該對象,就直接將該對象賦為前引用。
2)軟可及對象(softly reachable):
不通過強引用訪問的對象,即不是強可及對象,但是可以通過軟引用訪問的對象就成為軟可及對象,軟可及對象就需要使用類SoftReference(java.lang.ref.SoftReference)。此種類型的引用主要用于內存比較敏感的高速緩存,而且此種引用還是具有較強的引用功能,當內存不夠的時候GC會回收這類內存,因此如果內存充足的時候,這種引用通常不會被回收的。不僅僅如此,這種引用對象在JVM里面保證在拋出OutOfMemory異常之前,設置成為null。通俗地講,這種類型的引用保證在JVM內存不足的時候全部被清楚,但是有個關鍵在于:垃圾收集器在運行時是否釋放軟可及對象是不確定的,而且使用垃圾回收算法并不能保證一次性尋找到所有的軟可及對象。當垃圾回收器每次運行的時候都可以隨意釋放不是強可及對象占用的內存,如果垃圾回收器找到了軟可及對象過后,可能會進行以下操作:
【1】將SoftReference對象的referent域設置成為null,從而使該對象不再引用heap對象。
【2】SoftReference引用過的內存堆上的對象一律被生命為finalizable。
【3】當內存堆上的對象finalize()方法被運行而且該對象占用的內存被釋放,SoftReference對象就會被添加到它的ReferenceQueue,前提條件是ReferenceQueue本身是存在的。
既然Java里面存在這樣的對象,那么我們在編寫代碼的時候如何創建這樣的對象呢?創建步驟如下:
先創建一個對象,并使用普通引用方式【強引用】,然后再創建一個SoftReference來引用該對象,最后將普通引用設置為null,通過這樣的方式,這個對象就僅僅保留了一個SoftReference引用,同時這種情況我們所創建的對象就是SoftReference對象。一般情況下,我們可以使用該引用來完成Cache功能,就是前邊說的用于高速緩存,保證最大限度使用內存而不會引起內存泄漏的情況。下邊的代碼段:
public static void main(String args[])
{
//創建一個強可及對象
A a = new A();
//創建這個對象的軟引用SoftReference
SoftReference sr = new SoftReference(a);
//將強引用設置為空,以遍垃圾回收器回收強引用
a = null;
//下次使用該對象的操作
if( sr != null ){
a = (A)sr.get();
}else{
//這種情況就是由于內存過低,已經將軟引用釋放了,因此需要重新裝載一次
a = new A();
sr = new SoftReference(a);
}
}
軟引用技術使得Java系統可以更好地管理內存,保持系統穩定,防止內存泄漏,避免系統崩潰,因此在處理一些內存占用大而且生命周期長使用不頻繁的對象可以使用該技術。
3)弱可及對象(weakly reachable):
不是強可及對象同樣也不是軟可及對象,僅僅通過弱引用WeakReference(java.lang.ref.WeakReference)訪問的對象,這種對象的用途在于規范化映射(canonicalized mapping),對于生存周期相對比較長而且重新創建的時候開銷少的對象,弱引用也比較有用,和軟引用對象不同的是,垃圾回收器如果碰到了弱可及對象,將釋放WeakReference對象的內存,但是垃圾回收器需要運行很多次才能夠找到弱可及對象。弱引用對象在使用的時候,可以配合ReferenceQueue類使用,如果弱引用被回收,JVM就會把這個弱引用加入到相關的引用隊列中去。最簡單的弱引用方法如以下代碼:
WeakReference weakWidget = new WeakReference(classA);
在上邊代碼里面,當我們使用weakWidget.get()來獲取classA的時候,由于弱引用本身是無法阻止垃圾回收的,所以我們也許會拿到一個null為返回。【*:這里提供一個小技巧,如果我們希望取得某個對象的信息,但是又不影響該對象的垃圾回收過程,我們就可以使用WeakReference來記住該對象,一般我們在開發調試器和優化器的時候使用這個是很好的一個手段。】
如果上邊的代碼部分,我們通過weakWidget.get()返回的是null就證明該對象已經被垃圾回收器回收了,而這種情況下弱引用對象就失去了使用價值,GC就會定義為需要進行清除工作。這種情況下弱引用無法引用任何對象,所以在JVM里面就成為了一個死引用,這就是為什么我們有時候需要通過ReferenceQueue類來配合使用的原因,使用了ReferenceQueue過后,就使得我們更加容易監視該引用的對象,如果我們通過一ReferenceQueue類來構造一個若引用,當若引用的對象已經被回收的時候,系統將自動使用對象引用隊列來代替對象引用,而且我們可以通過ReferenceQueue類的運行來決定是否真正要從垃圾回收器里面將該死引用(Dead Reference)清除。
弱引用代碼段:
//創建普通引用對象
MyObject object = new MyObject();
//創建一個引用隊列
ReferenceQueue rq = new ReferenceQueue();
//使用引用隊列創建MyObject的弱引用
WeakReference wr = new WeakReference(object,rq);
這里提供兩個實在的場景來描述弱引用的相關用法:
(1)你想給對象附加一些信息,于是你用一個 Hashtable 把對象和附加信息關聯起來。你不停的把對象和附加信息放入 Hashtable 中,但是當對象用完的時候,你不得不把對象再從 Hashtable 中移除,否則它占用的內存變不會釋放。萬一你忘記了,那么沒有從 Hashtable 中移除的對象也可以算作是內存泄漏。理想的狀況應該是當對象用完時,Hashtable 中的對象會自動被垃圾收集器回收,不然你就是在做垃圾回收的工作。
(2)你想實現一個圖片緩存,因為加載圖片的開銷比較大。你將圖片對象的引用放入這個緩存,以便以后能夠重新使用這個對象。但是你必須決定緩存中的哪些圖片不再需要了,從而將引用從緩存中移除。不管你使用什么管理緩存的算法,你實際上都在處理垃圾收集的工作,更簡單的辦法(除非你有特殊的需求,這也應該是最好的辦法)是讓垃圾收集器來處理,由它來決定回收哪個對象。
當Java回收器遇到了弱引用的時候有可能會執行以下操作:
【1】將WeakReference對象的referent域設置成為null,從而使該對象不再引用heap對象。
【2】WeakReference引用過的內存堆上的對象一律被生命為finalizable。
【3】當內存堆上的對象finalize()方法被運行而且該對象占用的內存被釋放,WeakReference對象就會被添加到它的ReferenceQueue,前提條件是ReferenceQueue本身是存在的。
4)清除:
當引用對象的referent域設置為null,并且引用類在內存堆中引用的對象聲明為可結束的時候,該對象就可以清除,清除不做過多的講述
5)虛可及對象(phantomly reachable):
不是強可及對象,也不是軟可及對象,同樣不是弱可及對象,之所以把虛可及對象放到最后來講,主要也是因為它的特殊性,有時候我們又稱之為“幽靈對象”,已經結束的,可以通過虛引用來訪問該對象。我們使用類PhantomReference(java.lang.ref.PhantomReference)來訪問,這個類只能用于跟蹤被引用對象進行的收集,同樣的,可以用于執行per-mortern清除操作。PhantomReference必須與ReferenceQueue類一起使用。需要使用ReferenceQueue是因為它能夠充當通知機制,當垃圾收集器確定了某個對象是虛可及對象的時候,PhantomReference對象就被放在了它的ReferenceQueue上,這就是一個通知,表明PhantomReference引用的對象已經結束,可以收集了,一般情況下我們剛好在對象內存在回收之前采取該行為。這種引用不同于弱引用和軟引用,這種方式通過get()獲取到的對象總是返回null,僅僅當這些對象在ReferenceQueue隊列里面的時候,我們可以知道它所引用的哪些對對象是死引用(Dead Reference)。而這種引用和弱引用的區別在于:
弱引用(WeakReference)是在對象不可達的時候盡快進入ReferenceQueue隊列的,在finalization方法執行和垃圾回收之前是確實會發生的,理論上這類對象是不正確的對象,但是WeakReference對象可以繼續保持Dead狀態,
虛引用(PhantomReference)是在對象確實已經從物理內存中移除過后才進入的ReferenceQueue隊列,而且get()方法會一直返回null
當垃圾回收器遇到了虛引用的時候將有可能執行以下操作:
【1】PhantomReference引用過的heap對象聲明為finalizable;
【2】虛引用在堆對象釋放之前就添加到了它的ReferenceQueue里面,這種情況使得我們可以在堆對象被回收之前采取操作(*:再次提醒,PhantomReference對象必須經過關聯的ReferenceQueue來創建,就是說必須和ReferenceQueue類配合操作)
看似沒有用處的虛引用,有什么用途呢?
1.首先,我們可以通過虛引用知道對象究竟什么時候真正從內存里面移除的,而且這也是唯一的途徑。
2.虛引用避過了finalize()方法,因為對于此方法的執行而言,虛引用真正引用到的對象是異常對象,若在該方法內要使用對象只能重建。一般情況垃圾回收器會輪詢兩次,一次標記為finalization,第二次進行真實的回收,而往往標記工作不能實時進行,或者垃圾回收其會等待一個對象去標記finalization。這種情況很有可能引起MemoryOut,而使用虛引用這種情況就會完全避免。因為虛引用在引用對象的過程不會去使得這個對象由Dead復活,而且這種對象是可以在回收周期進行回收的。
在JVM內部,虛引用比起使用finalize()方法更加安全一點而且更加有效。而finaliaze()方法回收在虛擬機里面實現起來相對簡單,而且也可以處理大部分工作,所以我們仍然使用這種方式來進行對象回收的掃尾操作,但是有了虛引用過后我們可以選擇是否手動操作該對象使得程序更加高效完美。
2.Java里面對象的生命周期
在JVM運行空間里面,對象整個聲明周期大致分為以下幾個階段:
創建階段(Creating)->應用階段(Using)->不可視階段(Invisible)->不可達階段(Unreachable)->可收集階段(Collected)->終結階段(Finalized)->釋放階段(Free)
【1】創建階段:
創建過程需要經過其中幾步:
為對象分配內存空間
開始構造對象
遞歸調用超類的構造方法
進行對象實例初始化和變量初始化
執行構造方法體
【2】應用階段特征:
系統至少維護著對象的一個強引用(Strong Reference)
所有該對象的引用全部是強引用,除非我們顯示聲明了軟引用、弱引用或者虛引用
【3】不可是視階段:
不可視階段就是我們在區域代碼中不可以再引用它,就是強引用已經消失,一般情況我們把這個時候的對象設置為null,其主要目的是讓JVM發現它,并且可以及時回收該對象所占用資源
【4】不可到達階段:
不可達階段的對象,在虛擬機所管理的對象引用根集合中再也找不到直接或間接的強引用,這些對象通常是指所有線程棧中的臨時變量以及相關引用,這種對象都是要預備回收的對象,但是這時候不能被GC直接回收。
【5】可收集階段、終結階段、釋放階段:
對象生命周期最后一個階段,這種階段的對象可能處于三種狀態:
垃圾回收器發現對象已經不可達
finalize方法已經被執行
對象已經被重用
3.總結
文章部分內容引用自此文,也有自己學習過程中的一些心得,希望大家多交流