JVM之深入解析強引用、軟引用、弱引用

關於強引用、軟引用、弱引用、幻象引用的區別,在很多公司的面試題中經常出現,可能有些小夥伴覺得這個知識點比較冷門,但其實大家在開發中經常用到,如new一個對象的時候就是強引用的應用。
在java語言中,除了原始數據類型(boolean、byte、short、char、int、float、double、long)的變量,其他所有都是所謂的引用類型,指向各種不同的對象。理解這些引用的區別,對於掌握java對象生命週期和JVM內部相關機制非常有幫助。也有助於更深刻的理解底層對象生命週期、垃圾收集機制等,對設計可靠的緩存框架、診斷應用OOM等問題也大有裨益。
這四種應用主要的區別體現在對象不同的可達性狀態和對垃圾收集的影響,他們之間的可達性狀態可以參看下圖:
1.強引用(strong reference)
強引用就是我們最常見的普通對象引用(如new 一個對象),只要還有強引用指向一個對象,就表明此對象還“活着”。在強引用面前,即使JVM內存空間不足,JVM寧願拋出OutOfMemoryError運行時錯誤(OOM),讓程序異常終止,也不會靠回收強引用對象來解決內存不足的問題。對於一個普通的對象,如果沒有其他的引用關係,只要超過了引用的作用域或者顯式地將相應(強)引用賦值爲null,就意味着此對象可以被垃圾收集了。但要注意的是,並不是賦值爲null後就立馬被垃圾回收,具體的回收時機還是要看垃圾收集策略的。
如Object obj = new Object();
2.軟引用(soft reference)
軟引用相對強引用要弱化一些,可以讓對象豁免一些垃圾收集。當內存空間足夠的時候,垃圾回收器不會回收它。只有當JVM認定內存空間不足時纔會去回收軟引用指向的對象。JVM會確保在拋出OOM前清理軟引用指向的對象,而且JVM是很聰明的,會盡可能優先回收長時間閒置不用的軟引用指向的對象,對那些剛構建的或剛使用過的軟引用指向的對象儘可能的保留。基於軟引用的這些特性,軟引用可以用來實現很多內存敏感點的緩存場景,即如果內存還有空閒,可以暫時緩存一些業務場景所需的數據,當內存不足時就可以清理掉,等後面再需要時,可以重新獲取並再次緩存。這樣就確保在使用緩存提升性能的同時,不會導致耗盡內存。
軟引用通常可以和一個引用隊列(ReferenceQueue)聯合使用,如果弱引用所引用的對象被垃圾回收,java虛擬機就會把這個軟引用加入到與之關聯的引用隊列中。
[Java] 純文本查看 複製代碼
1
2
3
4
5
Object obj = new Object();
SoftReference<Object> sf = new SoftReference<Object>(obj);
obj = null;
//有時候會返回null
sf.get(); 
通過上面的代碼可以看出sf是對obj的一個軟引用,當sf對象還沒有被銷燬前,sf.get()可以獲取到這個對象,如果已被銷燬,則返回null。
正確使用軟引用的示例代碼如下:
[Java] 純文本查看 複製代碼
01
02
03
04
05
06
07
08
09
10
11
12
SoftReference<List<Foo>> ref = new SoftReference<List<Foo>>(new LinkedList<Foo>());
  
// somewhere else in your code, you create a Foo that you want to add to the list
List<Foo> list = ref.get();
if (list != null)
{
    list.add(foo);
}
else
{
    // list is gone; do whatever is appropriate
} 
在使用軟引用的時候必須檢查引用是否爲null。因爲垃圾收集器可能在任意時刻回收軟引用,如果不做是否null的判斷,可能會出現NullPointerException的異常。
總的來說,軟引用是用來描述一些還有用但並非必需的對象。對於軟引用關聯着的對象,在系統將要發生內存溢出異常之前,將會把這些對象列進回收範圍之中進行第二次回收。如果這次回收還沒有足夠的內存,纔會拋出內存溢出異常。
3.弱引用(weak reference)
弱引用指向的對象是一種十分臨近finalize狀態的情況,當弱引用被清除的時候,就符合finalize的條件了。弱引用與軟引用最大的區別就是弱引用比軟引用的生命週期更短暫。垃圾回收器會掃描它所管轄的內存區域的過程中,只要發現弱引用的對象,不管內存空間是否有空閒,都會立刻回收它。如同前面我說過的,具體的回收時機還是要看垃圾回收策略的,因此那些弱引用的對象並不是說只要達到弱引用狀態就會立馬被回收。
基於弱引用的這些特性,弱引用同樣可以應用在很多需要緩存的場景。
[Java] 純文本查看 複製代碼
1
2
3
4
5
6
7
Object obj = new Object();
WeakReference<Object> wf = new WeakReference<Object>(obj);
obj = null;
//有時候會返回null
wf.get();
//返回是否被垃圾回收器標記爲即將回收的垃圾
wf.isEnQueued();
4.幻象引用(phantom reference)
幻象引用,也有被說成是虛引用或幽靈引用。幻象引用並不會決定對象的生命週期。即如果一個對象僅持有虛引用,就相當於沒有任何引用一樣,在任何時候都可能被垃圾回收器回收。不能通過它訪問對象,幻象引用僅僅是提供了一種確保對象被finalize以後,做某些事情的機制(如做所謂的Post-Mortem清理機制),也有人利用幻象引用監控對象的創建和銷燬。
[Java] 純文本查看 複製代碼
1
2
3
4
5
6
7
Object obj = new Object();
PhantomReference<Object> pf = new PhantomReference<Object>(obj);
obj=null;
//永遠返回null
pf.get();
//返回是否從內存中已經刪除
pf.isEnQueued(); 
幻象引用的get方法永遠返回null,主要用於檢查對象是否已經從內存中刪除。
5.生存還是死亡
通過上面對四種引用類型的分析,你可能發現有些對象即使不可達,但也並非是“非死不可”的,這個時候它們暫時處於“緩刑”階段,要真正宣告一個對象死亡,至少要經歷兩次標記過程如果對象在進行可達性分析後發現沒有與GC Roots相連接的引用鏈,那它將會被第一次標記並且進行一次篩選,篩選的條件是此對象是否有必要執行finalize()方法。當對象沒有覆蓋finalize()方法,或者finalize()方法已經被虛擬機調用過,虛擬機將這兩種情況都視爲“沒有必要執行”。
如果這個對象被判定爲有必要執行finalize()方法,那麼這個對象將會放置在一個叫做F-Queue的隊列之中,並在稍後被一個由虛擬機自動建立的、低優先級的Finalizer線程去執行它。這裏所謂的“執行”是指虛擬機會觸發這個方法,但並不承諾會等待它運行結束,這樣做的原因是,如果一個對象在finalize()方法中執行緩慢,或者發生了死循環(更極端的情況),將很可能會導致F-Queue隊列中其他對象永久處於等待,甚至導致整個內存回收系統奔潰。finalize()方法是對象逃脫死亡命運的最後一次機會,稍後GC將對F-Queue中的對象進行第二次小規模的標記,如果對象要在finalize()中成功拯救自己——只要重新與引用鏈上的任何一個對象建立關聯即可。譬如把自己(this關鍵字)賦值給某個類變量或者對象的成員變量,那在第二次標記時它將被移除出“即將回收”的集合;如果對象這時候還沒有逃脫,那基本上它就真的被回收了。
任何一個對象的finalize()方法都只會被系統自動調用一次,如果對象面臨下一次回收,它的finalize()方法不會被再次執行。
6.總結
對象的可達性是JVM垃圾收集器決定如何處理對象的一個重要考慮指標
所有引用類型都是抽象類java.lang.ref.Reference的子類,子類裏提供了get()方法。通過上面的分析中可以得知,除了幻象引用(因爲get永遠返回null),如果對象還沒有被銷燬,都可以通過get方法獲取原有對象。其實有個非常關鍵的注意點,利用軟引用和弱引用,我們可以將訪問到的對象,重新指向強引用,也就是人爲的改變了對象的可達性狀態。所以對於軟引用、弱引用之類,垃圾收集器可能會存在二次確認的問題,以確保處於弱引用狀態的對象沒有改變爲強引用。
但是有個問題,如果我們錯誤的保持了強引用(比如,賦值給了static變量),那麼對象可能就沒有機會變回類似弱引用的可達性狀態了,就會產生內存泄露。所以,檢查弱引用指向對象是否被垃圾收集,也是診斷是否有特定內存泄露的一個思路,我們的框架使用到弱引用又懷疑有內存泄露,就可以從這個角度檢查。
對於軟引用、弱引用、幻象引用可以配合引用隊列(ReferenceQueue)來使用,特別是幻象引用,get方法只返回null,如果再不指定引用隊列,基本就沒有任何意義了。
上面分析了四種引用類型的使用,熟悉這幾種應用類型對深入理解JVM也大有裨益。

 

更多免費技術資料可關注:annalin1203

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章