Java中的引用類型
Java中存在四種引用,它們由強到弱依次是:強引用、軟引用、弱引用、虛引用。下面我們簡單介紹下除弱引用外的其他三種引用:
- 強引用(Strong Reference):通常我們通過new來創(chuàng)建一個新對象時返回的引用就是一個強引用,若一個對象通過一系列強引用可到達,它就是強可達的(strongly reachable),那么它就不被回收
- 弱引用(Weak Reference):弱引用的對象擁有更短暫的生命周期。在垃圾回收器線程掃描它所管轄的內(nèi)存區(qū)域的過程中,一旦發(fā)現(xiàn)了只具有弱引用的對象,不管當前內(nèi)存空間足夠與否,都會回收它的內(nèi)存
- 軟引用(Soft Reference):軟引用和弱引用的區(qū)別在于,若一個對象是弱引用可達,無論當前內(nèi)存是否充足它都會被回收,而軟引用可達的對象在內(nèi)存不充足時才會被回收,因此軟引用要比弱引用“強”一些
- 虛引用(Phantom Reference):虛引用是Java中最弱的引用,那么它弱到什么程度呢?它是如此脆弱以至于我們通過虛引用甚至無法獲取到被引用的對象,虛引用存在的唯一作用就是當它指向的對象被回收后,虛引用本身會被加入到引用隊列中,用作記錄它指向的對象已被回收。
判斷弱引用對象的關(guān)鍵在于只具有弱引用的對象,也就是說,如果一個對象有強引用,那么在系統(tǒng)GC時,是不會回收此對象的,也不會釋放弱引用。
為什么使用弱引用
Java常通過使用弱引用來避免內(nèi)存泄漏,例如在JDK中有一種內(nèi)存變量ThreadLocal,通過ThreadLocal變量可以使共享的變量在不同的線程中有不同的副本,原理是在每一個Thread有一個threadLocalMap的屬性,用來存放ThreadLocal對象,ThreadLocalMap中是通過一個Entry[]的散列表存放ThreadLocal變量以及ThreadLocal的value,而作為Entry的key的ThreadLocal就是使用的弱引用,結(jié)構(gòu)如下:
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
Entry通過繼承了WeakReference并通過get、set設(shè)置ThreadLocal為Entry的referent。
這里為什么要使用弱引用呢?
原因是如果不使用弱引用,那么當持有value的強引用釋放掉后,當線程沒有回收釋放時,threadLocalMap會一直持有ThreadLocal以及value的強應用,導致value不能夠被回收,從而造成內(nèi)存泄漏。
通過使用弱引用,當ThreadLocal的強引用釋放掉后,通過一次系統(tǒng)gc檢查,發(fā)現(xiàn)ThreadLocal對象只有threadLocalMap中Entry的若引用持有,此時根據(jù)弱引用的機制就會回收ThreadLocal對象,從而避免了內(nèi)存泄露。當然ThreadLocal還有一些額外的保護措施,詳細分析可以參考:死磕Java源碼之ThreadLocal實現(xiàn)分析
這里我們可以通過一個示例來驗證一下:
WeakReferenceDemo.java
import java.lang.ref.WeakReference;
/**
* 弱引用回收測試
*/
public class WeakReferenceDemo {
public static WeakReference<String> weakReference1;
public static WeakReference<String> weakReference2;
public static void main(String[] args) {
test1();
//可以輸出hello值,此時兩個弱引用扔持有對象,而且未進行g(shù)c
System.out.println("未進行g(shù)c時,只有弱引用指向value內(nèi)存區(qū)域:" + weakReference1.get());
//此時已無強一用執(zhí)行"value"所在內(nèi)存區(qū)域,gc時會回收弱引用
System.gc();
//此時輸出都為nuill
System.out.println("進行g(shù)c時,只有弱引用指向value內(nèi)存區(qū)域:" + weakReference1.get());
}
public static void test1() {
String hello = new String("value");
weakReference1 = new WeakReference<>(hello);
System.gc();
//此時gc不會回收弱引用,因為字符串"value"仍然被hello對象強引用
System.out.println("進行g(shù)c時,強引用與弱引用同時指向value內(nèi)存區(qū)域:" + weakReference1.get());
}
}
輸出:
進行g(shù)c時,強引用與弱引用同時指向value內(nèi)存區(qū)域:value
未進行g(shù)c時,只有弱引用指向value內(nèi)存區(qū)域:value
進行g(shù)c時,只有弱引用指向value內(nèi)存區(qū)域:null
分析輸出結(jié)果可以看出:
當有強引用指向value內(nèi)存區(qū)域時,即使進行g(shù)c,弱引用也不會被釋放,對象不回被回收。
當無強引用指向value內(nèi)存區(qū)域是,此時進行g(shù)c,弱引用會被釋放,對象將會執(zhí)行回收流程。
引用隊列
下面我們來簡單地介紹下引用隊列的概念。實際上,WeakReference類有兩個構(gòu)造函數(shù):
//創(chuàng)建一個指向給定對象的弱引用``WeakReference(T referent)
//創(chuàng)建一個指向給定對象并且登記到給定引用隊列的弱引用``WeakReference(T referent, ReferenceQueue<? ``super` `T> q)
我們可以看到第二個構(gòu)造方法中提供了一個ReferenceQueue類型的參數(shù),通過提供這個參數(shù),我們便把創(chuàng)建的弱引用對象注冊到了一個引用隊列上,這樣當它被垃圾回收器清除時,就會把它送入這個引用隊列中,我們便可以對這些被清除的弱引用對象進行統(tǒng)一管理。
參考:
http://m.itdecent.cn/p/640f2c0ac4b0
https://www.cnblogs.com/fengbs/p/7019687.html
http://www.importnew.com/21206.html