一文讀懂java中的Reference和引用類型java
java中有值類型也有引用類型,引用類型通常是針對於java中對象來講的,今天介紹一下java中的引用類型。java爲引用類型專門定義了一個類叫作Reference。Reference是跟java垃圾回收機制息息相關的類,經過探討Reference的實現能夠更加深刻的理解java的垃圾回收是怎麼工做的。git
本文先從java中的四種引用類型開始,一步一步揭開Reference的面紗。程序員
java中的四種引用類型分別是:強引用,軟引用,弱引用和虛引用。github
java中的引用默認就是強引用,任何一個對象的賦值操做就產生了對這個對象的強引用。函數
咱們看一個例子:this
public class StrongReferenceUsage {
@Test
public void stringReference(){
Object obj = new Object();
}
}
複製代碼
上面咱們new了一個Object對象,並將其賦值給obj,這個obj就是new Object()的強引用。spa
強引用的特性是隻要有強引用存在,被引用的對象就不會被垃圾回收。3d
軟引用在java中有個專門的SoftReference類型,軟引用的意思是隻有在內存不足的狀況下,被引用的對象纔會被回收。code
先看下SoftReference的定義:cdn
public class SoftReference<T> extends Reference<T> 複製代碼
SoftReference繼承自Reference。它有兩種構造函數:
public SoftReference(T referent) 複製代碼
和:
public SoftReference(T referent, ReferenceQueue<? super T> q) 複製代碼
第一個參數很好理解,就是軟引用的對象,第二個參數叫作ReferenceQueue,是用來存儲封裝的待回收Reference對象的,ReferenceQueue中的對象是由Reference類中的ReferenceHandler內部類進行處理的。
咱們舉個SoftReference的例子:
@Test
public void softReference(){
Object obj = new Object();
SoftReference<Object> soft = new SoftReference<>(obj);
obj = null;
log.info("{}",soft.get());
System.gc();
log.info("{}",soft.get());
}
複製代碼
輸出結果:
22:50:43.733 [main] INFO com.flydean.SoftReferenceUsage - java.lang.Object@71bc1ae4
22:50:43.749 [main] INFO com.flydean.SoftReferenceUsage - java.lang.Object@71bc1ae4
複製代碼
能夠看到在內存充足的狀況下,SoftReference引用的對象是不會被回收的。
weakReference和softReference很相似,不一樣的是weekReference引用的對象只要垃圾回收執行,就會被回收,而不論是否內存不足。
一樣的WeakReference也有兩個構造函數:
public WeakReference(T referent); public WeakReference(T referent, ReferenceQueue<? super T> q); 複製代碼
含義和SoftReference一致,這裏就再也不重複表述了。
咱們看下弱引用的例子:
@Test
public void weakReference() throws InterruptedException {
Object obj = new Object();
WeakReference<Object> weak = new WeakReference<>(obj);
obj = null;
log.info("{}",weak.get());
System.gc();
log.info("{}",weak.get());
}
複製代碼
輸出結果:
22:58:02.019 [main] INFO com.flydean.WeakReferenceUsage - java.lang.Object@71bc1ae4
22:58:02.047 [main] INFO com.flydean.WeakReferenceUsage - null
複製代碼
咱們看到gc事後,弱引用的對象被回收掉了。
PhantomReference的做用是跟蹤垃圾回收器收集對象的活動,在GC的過程當中,若是發現有PhantomReference,GC則會將引用放到ReferenceQueue中,由程序員本身處理,當程序員調用ReferenceQueue.pull()方法,將引用出ReferenceQueue移除以後,Reference對象會變成Inactive狀態,意味着被引用的對象能夠被回收了。
和SoftReference和WeakReference不一樣的是,PhantomReference只有一個構造函數,必須傳入ReferenceQueue:
public PhantomReference(T referent, ReferenceQueue<? super T> q) 複製代碼
看一個PhantomReference的例子:
@Slf4j
public class PhantomReferenceUsage {
@Test
public void usePhantomReference(){
ReferenceQueue<Object> rq = new ReferenceQueue<>();
Object obj = new Object();
PhantomReference<Object> phantomReference = new PhantomReference<>(obj,rq);
obj = null;
log.info("{}",phantomReference.get());
System.gc();
Reference<Object> r = (Reference<Object>)rq.poll();
log.info("{}",r);
}
}
複製代碼
運行結果:
07:06:46.336 [main] INFO com.flydean.PhantomReferenceUsage - null
07:06:46.353 [main] INFO com.flydean.PhantomReferenceUsage - java.lang.ref.PhantomReference@136432db
複製代碼
咱們看到get的值是null,而GC事後,poll是有值的。
由於PhantomReference引用的是須要被垃圾回收的對象,因此在類的定義中,get一直都是返回null:
public T get() {
return null;
}
複製代碼
講完上面的四種引用,接下來咱們談一下他們的父類Reference和ReferenceQueue的做用。
Reference是一個抽象類,每一個Reference都有一個指向的對象,在Reference中有5個很是重要的屬性:referent,next,discovered,pending,queue。
private T referent; /* Treated specially by GC */
volatile ReferenceQueue<? super T> queue;
Reference next;
transient private Reference<T> discovered; /* used by VM */
private static Reference<Object> pending = null;
複製代碼
每一個Reference均可以當作是一個節點,多個Reference經過next,discovered和pending這三個屬性進行關聯。
先用一張圖來對Reference有個總體的概念:
referent就是Reference實際引用的對象。
經過next屬性,能夠構建ReferenceQueue。
經過discovered屬性,能夠構建Discovered List。
經過pending屬性,能夠構建Pending List。
在講這三個Queue/List以前,咱們先講一下Reference的四個狀態:
從上面的圖中,咱們能夠看到一個Reference能夠有四個狀態。
由於Reference有兩個構造函數,一個帶ReferenceQueue,一個不帶。
Reference(T referent) {
this(referent, null);
}
Reference(T referent, ReferenceQueue<? super T> queue) {
this.referent = referent;
this.queue = (queue == null) ? ReferenceQueue.NULL : queue;
}
複製代碼
對於帶ReferenceQueue的Reference,GC會把要回收對象的Reference放到ReferenceQueue中,後續該Reference須要程序員本身處理(調用poll方法)。
不帶ReferenceQueue的Reference,由GC本身處理,待回收的對象其Reference狀態會變成Inactive。
建立好了Reference,就進入active狀態。
active狀態下,若是引用對象的可到達狀態發送變化就會轉變成Inactive或Pending狀態。
Inactive狀態很好理解,到達Inactive狀態的Reference狀態不能被改變,會等待GC回收。
Pending狀態表明等待入Queue,Reference內部有個ReferenceHandler,會調用enqueue方法,將Pending對象入到Queue中。
入Queue的對象,其狀態就變成了Enqueued。
Enqueued狀態的對象,若是調用poll方法從ReferenceQueue拿出,則該Reference的狀態就變成了Inactive,等待GC的回收。
這就是Reference的一個完整的生命週期。
有了上面四個狀態的概念,咱們接下來說三個Queue/List:ReferenceQueue,discovered List和pending List。
ReferenceQueue在講狀態的時候已經講過了,它本質是由Reference中的next鏈接而成的。用來存儲GC待回收的對象。
pending List就是待入ReferenceQueue的list。
discovered List這個有點特別,在Pending狀態時候,discovered List就等於pending List。
在Active狀態的時候,discovered List實際上維持的是一個引用鏈。經過這個引用鏈,咱們能夠得到引用的鏈式結構,當某個Reference狀態再也不是Active狀態時,須要將這個Reference從discovered List中刪除。
最後講一下WeakHashMap,WeakHashMap跟WeakReference有點相似,在WeakHashMap若是key再也不被使用,被賦值爲null的時候,該key對應的Entry會自動從WeakHashMap中刪除。
咱們舉個例子:
@Test
public void useWeakHashMap(){
WeakHashMap<Object, Object> map = new WeakHashMap<>();
Object key1= new Object();
Object value1= new Object();
Object key2= new Object();
Object value2= new Object();
map.put(key1, value1);
map.put(key2, value2);
log.info("{}",map);
key1 = null;
System.gc();
log.info("{}",map);
}
複製代碼
輸出結果:
[main] INFO com.flydean.WeakHashMapUsage - {java.lang.Object@14899482=java.lang.Object@2437c6dc, java.lang.Object@11028347=java.lang.Object@1f89ab83}
[main] INFO com.flydean.WeakHashMapUsage - {java.lang.Object@14899482=java.lang.Object@2437c6dc}
複製代碼
能夠看到gc事後,WeakHashMap只有一個Entry了。
本文講解了4個java中的引用類型,並深刻探討了Reference的內部機制,感興趣的小夥伴能夠留言一塊兒討論。
歡迎關注個人公衆號:程序那些事,更多精彩等着您! 更多內容請訪問 www.flydean.com