Java 中的四種引用
之前我們提到過(guò) GC,但當(dāng) Java 中引用的對(duì)象越來(lái)越多,會(huì)導(dǎo)致內(nèi)存空間不足,最終會(huì)產(chǎn)生錯(cuò)誤 OutOfMemoryError,并讓應(yīng)用程序終止。那為什么 GC 在此時(shí)不能多收集一些對(duì)象呢?這就和今天說(shuō)的引用類型有關(guān)了。
首先,從 JDK1.2 開始,對(duì)象的引用被劃分為4種級(jí)別,從而使程序能更加靈活地控制對(duì)象的生命周期。這4種級(jí)別由高到低依次為:強(qiáng)引用、軟引用、弱引用和虛引用。
強(qiáng)引用
強(qiáng)引用(Strong Reference)是使用最普遍的引用。如果一個(gè)對(duì)象具有強(qiáng)引用,那么它永遠(yuǎn)不會(huì)被 GC。例如:
- Object strongReference = new Object();
 
當(dāng)內(nèi)存空間不足時(shí),JVM 寧愿拋出OutOfMemoryError,使程序異常終止,也不會(huì)靠隨意回收具有強(qiáng)引用的對(duì)象來(lái)解決內(nèi)存不足的問(wèn)題。
如果強(qiáng)引用對(duì)象不使用時(shí),需要弱化從而可以被 GC,例如ArrayList中的clear()方法:
- /**
 - * Removes all of the elements from this list. The list will
 - * be empty after this call returns.
 - */
 - public void clear() {
 - modCount++;
 - // clear to let GC do its work
 - for (int i = 0; i < size; i++)
 - elementData[i] = null;
 - size = 0;
 - }
 
顯式地設(shè)置強(qiáng)引用對(duì)象為null,或讓其超出對(duì)象的生命周期范圍,則垃圾回收器認(rèn)為該對(duì)象不存在引用,就會(huì)回收這個(gè)對(duì)象。具體什么時(shí)候收集這要取決于具體的垃圾回收器。
軟引用
如果一個(gè)對(duì)象只具有軟引用(Soft Reference),當(dāng)內(nèi)存空間充足時(shí),垃圾回收器就不會(huì)回收它;如果內(nèi)存空間不足了,就會(huì)回收這些對(duì)象的內(nèi)存。只要垃圾回收器沒(méi)有回收它,該對(duì)象就可以被程序使用。讓我們來(lái)看一個(gè)例子具體了解一下:
- String str = new String("abc");
 - SoftReference<String> softReference = new SoftReference<>(str);
 - String result = softReference.get();
 
讓我們來(lái)看一下get():
- public T get() {
 - T o = super.get();
 - // timestamp代表上一次軟引用上一次被使用的時(shí)間(初始化、get())
 - // clock代表上一次GC的時(shí)間
 - if (o != null && this.timestamp != clock)
 - this.timestamp = clock;
 - return o;
 - }
 
因此,軟引用在被垃圾回收時(shí),也遵循LRU法則,優(yōu)先回收最近最少被使用的對(duì)象進(jìn)行回收。
軟引用的使用場(chǎng)景多是內(nèi)存敏感的高速緩存。具體來(lái)說(shuō),就是我們希望將數(shù)據(jù)存放到緩存中,這樣可以快速進(jìn)行讀取。但是,當(dāng) JVM 中內(nèi)存不夠用時(shí),我們又不希望緩存數(shù)據(jù)會(huì)占用到 JVM 的內(nèi)存。例如配合ReferenceQueue,如果軟引用所引用對(duì)象被垃圾回收,JVM 就會(huì)把這個(gè)軟引用加入到與之關(guān)聯(lián)的引用隊(duì)列中:
- ReferenceQueue<String> referenceQueue = new ReferenceQueue<>();
 - String str = new String("abc");
 - SoftReference<String> softReference = new SoftReference<>(str, referenceQueue);
 - str = null;
 - // Notify GC
 - System.gc();
 - System.out.println(softReference.get()); // abc
 - Reference<? extends String> reference = referenceQueue.poll();
 - System.out.println(reference); //null
 
但是需要注意的是,如果使用軟引用緩存,有可能導(dǎo)致Full GC增多。
弱引用
如果一個(gè)對(duì)象只具有弱引用(Weak Reference),其生命周期相比于軟引用更加短暫。在垃圾回收器線程掃描它所管轄的內(nèi)存區(qū)域的過(guò)程中,一旦發(fā)現(xiàn)了只具有弱引用的對(duì)象,不管當(dāng)前內(nèi)存空間足夠與否,都會(huì)對(duì)它進(jìn)行回收。不過(guò),由于垃圾回收器是一個(gè)優(yōu)先級(jí)很低的線程,因此不一定會(huì)很快發(fā)現(xiàn)那些只具有弱引用的對(duì)象。其使用為:
- String str = new String("abc");
 - WeakReference<String> weakReference = new WeakReference<>(str);
 - str = weakReference.get();
 
講到弱引用,就不得不提到WeakHashMap。和HashMap相比,當(dāng)我們給 JVM 分配的內(nèi)存不足的時(shí)候,HashMap 寧可拋出 OutOfMemoryError 異常,也不會(huì)回收其相應(yīng)的沒(méi)有被引用的對(duì)象,而 WeakHashMap 則會(huì)回收存儲(chǔ)在其中但有被引用的對(duì)象。
WeakHashMap 通過(guò)將一些沒(méi)有被引用的鍵的值賦值為 null ,這樣的話就會(huì)告知GC去回收這些存儲(chǔ)的值了。假如我們特地傳入 key 為 null 的鍵,WeakHashMap 會(huì)將鍵設(shè)置為特殊的 Oject,源碼為:
- public V put(K key, V value) {
 - // key會(huì)被重新賦值
 - Object k = maskNull(key);
 - int h = hash(k);
 - Entry<K,V>[] tab = getTable();
 - int i = indexFor(h, tab.length);
 - for (Entry<K,V> e = tab[i]; e != null; ee = e.next) {
 - if (h == e.hash && eq(k, e.get())) {
 - V oldValue = e.value;
 - if (value != oldValue)
 - e.value = value;
 - return oldValue;
 - }
 - }
 - modCount++;
 - Entry<K,V> e = tab[i];
 - tab[i] = new Entry<>(k, value, queue, h, e);
 - if (++size >= threshold)
 - resize(tab.length * 2);
 - return null;
 - }
 - /**
 - * Value representing null keys inside tables.
 - * 特殊的key
 - */
 - private static final Object NULL_KEY = new Object();
 - /**
 - * Use NULL_KEY for key if it is null.
 - */
 - private static Object maskNull(Object key) {
 - return (key == null) ? NULL_KEY : key;
 - }
 
虛引用
虛引用(PhantomReference),顧名思義,就是形同虛設(shè)。與其他幾種引用都不同,虛引用并不會(huì)決定對(duì)象的生命周期。如果一個(gè)對(duì)象僅持有虛引用,那么它就和沒(méi)有任何引用一樣,在任何時(shí)候都可能被垃圾回收器回收。
虛引用主要用來(lái)跟蹤對(duì)象被垃圾回收器回收的活動(dòng)。 虛引用與軟引用和弱引用的一個(gè)區(qū)別在于:
虛引用必須和引用隊(duì)列(ReferenceQueue)聯(lián)合使用。當(dāng)垃圾回收器準(zhǔn)備回收一個(gè)對(duì)象時(shí),如果發(fā)現(xiàn)它還有虛引用,就會(huì)在回收對(duì)象的內(nèi)存之前,把這個(gè)虛引用加入到與之關(guān)聯(lián)的引用隊(duì)列中。
例如:
- String str = new String("abc");
 - ReferenceQueue queue = new ReferenceQueue();
 - // 創(chuàng)建虛引用,要求必須與一個(gè)引用隊(duì)列關(guān)聯(lián)
 - PhantomReference pr = new PhantomReference(str, queue);
 
程序可以通過(guò)判斷引用隊(duì)列中是否已經(jīng)加入了虛引用,來(lái)了解被引用的對(duì)象是否將要進(jìn)行垃圾回收。如果程序發(fā)現(xiàn)某個(gè)虛引用已經(jīng)被加入到引用隊(duì)列,那么就可以在所引用的對(duì)象的內(nèi)存被回收之前采取必要的行動(dòng),也可以理解為一種回調(diào)方法。
總結(jié)
Java 中4種引用的級(jí)別和強(qiáng)度由高到低依次為:強(qiáng)引用 -> 軟引用 -> 弱引用 -> 虛引用
通過(guò)表格,說(shuō)明其特性:
















 
 
 



 
 
 
 