深入理解Java中的引用(二)——強軟弱虛引用
在上一篇文章中介紹了Java的Reference類,本篇文章介紹他的四個子類:強引用、軟引用、弱引用、虛引用。
強引用(StrongReference)
強引用是我們在代碼中最普通的引用。示例代碼如下:
Object o = new Object(); // 強引用
在JVM的GC算法中,如果一個對象具有強引用,那么JVM寧可拋出Out of Memory錯誤,垃圾回收器也不會去回收這個對象。
當在代碼里顯示的寫o = null,或者該對象的引用作用域是在一個函數里,代碼如下,當線程調用完test,就會退出方法棧,引用不存在,垃圾回收器才會在某個時刻回收Object對象。
public void test(){
Object o = new Object(); // 強引用
}
軟引用(SoftReference)
如果一個對象有一個軟引用,那么在內存足夠的情況下,該對象就不會被垃圾回收器回收。網上有很多資料說軟引用只會在內存空間不夠用的情況下對象才會被回收。 那么什么時候才是內存不夠用呢?
首先看一下SoftReference類的源碼可以看到有兩個字段。這兩個字段的作用已經標注,這與JVM GC有什么關系呢?
/**
* 記錄最近一次被GC的時間。
*/
static private long clock;
/**
* 每次調用get方法的時候更新
* 記錄當前Reference最近一次被訪問的時間
*/
private long timestamp;
一起看一下HotSpot的源碼,對于軟引用的回收策略見下面should_clear_reference函數。
// 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的賦值函數如下:
// 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之后還有很多剩余空間,說明內存空間不夠用了,那么max_heap的值就越小,相應_max_interval也越小,軟引用就越可能被回收。
軟引用的一個作用是實現內存敏感的高速緩存。比如瀏覽器的后退按鈕,
(1)如果網頁瀏覽結束就進行內容的回收,則按后退查看前面瀏覽過的頁面時,需要重新構建。
(2)如果將瀏覽過的網頁存儲到內存中會造成內存的大量浪費,甚至會造成內存溢出。
通過軟引用可以解決該問題
Browser prev = new Browser(); // 獲取頁面進行瀏覽
SoftReference sr = new SoftReference(prev); // 瀏覽完畢后置為軟引用
if(sr.get()!=null){
rev = (Browser) sr.get(); // 還沒有被回收器回收,直接獲取
}else{
prev = new Browser(); // 由于內存吃緊,所以對軟引用的對象回收了
sr = new SoftReference(prev); // 重新構建
}
弱引用(WeakReference)
只具有弱引用的對象生命周期更短。當垃圾回收器發現了只有弱引用的對象時候,無論內存空間是否足夠,都會被GC回收。當你偶爾需要引用某個對象,隨時能獲取該對象,但是不想介入該對象的生命周期的時候,就可以使用弱引用, 因為弱引用不會對對象的垃圾回收判斷產生附加的影響。
當弱引用綁定的對象被垃圾回收的時候,JVM會把這個弱引用加入到相關聯的ReferenceQueue中。
這里拋出兩個問題:
(1)弱引用什么時候會被加入到ReferenceQueue中,由什么決定的呢?
(2)如果綁定的對象GC之后存活了下來,弱引用怎么知道這個對象的新地址呢?
第一種情況,GC掃描到只存在弱引用的時候就會把它放到鏈表里。還有第二種情況:一個對象既有強引用又有弱引用的情況
下面通過圖片來解釋上面兩個問題:
image.png
上圖表示C同時存在兩個引用:強引用A和弱引用B。
第一種情況:GC先掃描到A
這種情況下GC同時會掃描到C,A和C都會搬到Survivor區。然后掃描到B,發現B引用的C搬到到了新的Survivor,這個時候就把B也搬到Survivor,并把C的新地址更新到B,結果如下:
image.png
第二種情況:GC先掃描到B
GC還是會先把B放到ReferenceQueue中,由于C還是存活的,所以B會被搬到Survivor中。然后掃描到A,A和C都會搬到Survivor中,GC結束的時候B所指向的對象就不對了,如下圖所示
image.png
該情況下會重新遍歷ReferenceQueue,發現綁定的對象依然存活,C‘ 的指針是指向C的,于是就把B再指向C就可以了,同時因為C依然存活,把B從ReferenceQueue中移除。新的地址空間如下圖所示:
image.png
具體過程可以查看海納知乎專欄。
虛引用(PhantomReference)
虛引用不會對對象的垃圾回收有任何附加影響,他與軟引用和弱引用的一個區別在于:虛引用必須和引用隊列 (ReferenceQueue)聯合使用。查看他的構造方法可以看到必須與一個ReferenceQueue綁定,而且他的get方法返回的一直是null
public T get() {
return null;
}
public PhantomReference(T referent, ReferenceQueue super T> q) {
super(referent, q);
}
虛引用主要用在跟蹤對象垃圾回收的狀態。具體應用會在在下一節講到DirectByteBuffer 與ThreadLoal回收的時候詳細分析。
總結
關于強引用、軟引用、弱引用與虛引用在垃圾回收時的區別可以用下圖表示:
image.png
下圖總結了四種引用在其他方面的區別:
image.png