深入理解Java中的引用(二)——強軟弱虛引用
在上一篇文章中介紹了Java的Reference類挥萌,本篇文章介紹他的四個子類:強引用蹈垢、軟引用八千、弱引用吗讶、虛引用燎猛。
強引用(StrongReference)
強引用是我們在代碼中最普通的引用恋捆。示例代碼如下:
Object o = new Object(); // 強引用
在JVM的GC算法中,如果一個對象具有強引用重绷,那么JVM寧可拋出Out of Memory
錯誤沸停,垃圾回收器也不會去回收這個對象。
當在代碼里顯示的寫o = null
昭卓,或者該對象的引用作用域是在一個函數(shù)里愤钾,代碼如下,當線程調(diào)用完test候醒,就會退出方法棧能颁,引用不存在,垃圾回收器才會在某個時刻回收Object對象倒淫。
public void test(){
Object o = new Object(); // 強引用
}
軟引用(SoftReference)
如果一個對象有一個軟引用伙菊,那么在內(nèi)存足夠的情況下,該對象就不會被垃圾回收器回收。網(wǎng)上有很多資料說軟引用只會在內(nèi)存空間不夠用的情況下對象才會被回收镜硕。 那么什么時候才是內(nèi)存不夠用呢运翼?
首先看一下SoftReference類的源碼可以看到有兩個字段。這兩個字段的作用已經(jīng)標注兴枯,這與JVM GC有什么關(guān)系呢血淌?
/**
* 記錄最近一次被GC的時間。
*/
static private long clock;
/**
* 每次調(diào)用get方法的時候更新
* 記錄當前Reference最近一次被訪問的時間
*/
private long timestamp;
一起看一下HotSpot的源碼财剖,對于軟引用的回收策略見下面should_clear_reference
函數(shù)悠夯。
// The oop passed in is the SoftReference object, and not
// the object the SoftReference points to.
bool LRUMaxHeapPolicy::should_clear_reference(oop p,
jlong timestamp_clock) {
jlong interval = timestamp_clock - java_lang_ref_SoftReference::timestamp(p);
assert(interval >= 0, "Sanity check");
// The interval will be zero if the ref was accessed since the last scavenge/gc.
if(interval <= _max_interval) {
return false;
}
return true;
}
上述代碼中interval
表示當前引用存活了多久。他的值就是對應上述java代碼中的clock
與timestamp
相減躺坟。interval
與_max_interval
比較疗疟,如果大于 _max_interval
,那么就和弱引用一樣處理瞳氓,如果小于就當做強引用處理策彤。_max_interval
的賦值函數(shù)如下:
// Capture state (of-the-VM) information needed to evaluate the policy
void LRUMaxHeapPolicy::setup() {
size_t max_heap = MaxHeapSize;
max_heap -= Universe::get_heap_used_at_last_gc();
max_heap /= M;
_max_interval = max_heap * SoftRefLRUPolicyMSPerMB;
assert(_max_interval >= 0,"Sanity check");
}
通過源碼可見首先是max_heap
減去上次GC之后剩余堆大小,如果上次GC之后還有很多剩余空間匣摘,說明內(nèi)存空間不夠用了店诗,那么max_heap
的值就越小,相應_max_interval
也越小音榜,軟引用就越可能被回收庞瘸。
軟引用的一個作用是實現(xiàn)內(nèi)存敏感的高速緩存。比如瀏覽器的后退按鈕赠叼,
(1)如果網(wǎng)頁瀏覽結(jié)束就進行內(nèi)容的回收擦囊,則按后退查看前面瀏覽過的頁面時,需要重新構(gòu)建嘴办。
(2)如果將瀏覽過的網(wǎng)頁存儲到內(nèi)存中會造成內(nèi)存的大量浪費瞬场,甚至會造成內(nèi)存溢出。
通過軟引用可以解決該問題
Browser prev = new Browser(); // 獲取頁面進行瀏覽
SoftReference sr = new SoftReference(prev); // 瀏覽完畢后置為軟引用
if(sr.get()!=null){
rev = (Browser) sr.get(); // 還沒有被回收器回收涧郊,直接獲取
}else{
prev = new Browser(); // 由于內(nèi)存吃緊贯被,所以對軟引用的對象回收了
sr = new SoftReference(prev); // 重新構(gòu)建
}
弱引用(WeakReference)
只具有弱引用的對象生命周期更短。當垃圾回收器發(fā)現(xiàn)了只有弱引用的對象時候妆艘,無論內(nèi)存空間是否足夠彤灶,都會被GC回收。當你偶爾需要引用某個對象批旺,隨時能獲取該對象幌陕,但是不想介入該對象的生命周期的時候,就可以使用弱引用汽煮, 因為弱引用不會對對象的垃圾回收判斷產(chǎn)生附加的影響搏熄。
當弱引用綁定的對象被垃圾回收的時候茅诱,JVM會把這個弱引用加入到相關(guān)聯(lián)的ReferenceQueue中。
這里拋出兩個問題:
(1)弱引用什么時候會被加入到ReferenceQueue中搬卒,由什么決定的呢瑟俭?
(2)如果綁定的對象GC之后存活了下來,弱引用怎么知道這個對象的新地址呢契邀?
第一種情況摆寄,GC掃描到只存在弱引用的時候就會把它放到鏈表里。還有第二種情況:一個對象既有強引用又有弱引用的情況
下面通過圖片來解釋上面兩個問題:
上圖表示C同時存在兩個引用:強引用A和弱引用B坯门。
第一種情況:GC先掃描到A
這種情況下GC同時會掃描到C微饥,A和C都會搬到Survivor區(qū)。然后掃描到B古戴,發(fā)現(xiàn)B引用的C搬到到了新的Survivor欠橘,這個時候就把B也搬到Survivor,并把C的新地址更新到B现恼,結(jié)果如下:
第二種情況:GC先掃描到B
GC還是會先把B放到ReferenceQueue中肃续,由于C還是存活的,所以B會被搬到Survivor中叉袍。然后掃描到A始锚,A和C都會搬到Survivor中,GC結(jié)束的時候B所指向的對象就不對了喳逛,如下圖所示
該情況下會重新遍歷ReferenceQueue瞧捌,發(fā)現(xiàn)綁定的對象依然存活,C‘ 的指針是指向C的润文,于是就把B再指向C就可以了姐呐,同時因為C依然存活,把B從ReferenceQueue中移除典蝌。新的地址空間如下圖所示:
具體過程可以查看海納知乎專欄曙砂。
虛引用(PhantomReference)
虛引用不會對對象的垃圾回收有任何附加影響,他與軟引用和弱引用的一個區(qū)別在于:虛引用必須和引用隊列 (ReferenceQueue)聯(lián)合使用赠法。查看他的構(gòu)造方法可以看到必須與一個ReferenceQueue綁定麦轰,而且他的get方法返回的一直是null
public T get() {
return null;
}
public PhantomReference(T referent, ReferenceQueue<? super T> q) {
super(referent, q);
}
虛引用主要用在跟蹤對象垃圾回收的狀態(tài)。具體應用會在在下一節(jié)講到DirectByteBuffer 與ThreadLoal回收的時候詳細分析砖织。
總結(jié)
關(guān)于強引用、軟引用末荐、弱引用與虛引用在垃圾回收時的區(qū)別可以用下圖表示:
下圖總結(jié)了四種引用在其他方面的區(qū)別: