虛引用對象到底什麼時候被回收?
晚上被這個問題幹了一個多小時。。。
問題來源代碼:
import java.lang.ref.PhantomReference;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
/**
* @Author: xiaoshijiu
* @Date: 2020/2/27
* @Description:
*/
public class PhantomReferenceDemo {
public static void main(String[] args) throws InterruptedException {
ReferenceQueue<byte[]> queue = new ReferenceQueue<>();
// 虛引用必須要和引用隊列一起使用,他的get方法永遠返回null
PhantomReference<byte[]> phantomReference = new PhantomReference<>(
new byte[1024 * 1024 * 5], queue);
System.out.println(queue.poll());
System.gc();
Thread.sleep(300L);
System.out.println(queue.poll());
byte[] bytes = new byte[1024 * 1024 * 6];
}
}
jvm設置了參數-Xmx10m -Xms10m,最大內存10m
運行結果一直是:
報OOM,表明不足6m去創建新的byte數組,也即前面的5m虛引用指向的byte數組沒有被垃圾回收;
我就很鬱悶了,進行了一次GC,虛引用也被添加到了與其關聯的引用隊列中,但是爲什麼虛引用所指向的對象,就一直沒有被回收呢?
這一點跟 軟引用和弱引用 真的不同啊
將上面代碼分別換成軟引用和弱引用,嘗試:
import java.lang.ref.SoftReference;
/**
* @Author: xiaoshijiu
* @Date: 2020/2/27
* @Description:
*/
public class SoftReferenceDemo {
public static void main(String[] args) throws InterruptedException {
// 軟引用測試
SoftReference<byte[]> phantomReference = new SoftReference<>(
new byte[1024 * 1024 * 5]);
byte[] bytes = new byte[1024 * 1024 * 6];
}
}
import java.lang.ref.WeakReference;
/**
* @Author: xiaoshijiu
* @Date: 2020/2/27
* @Description:
*/
public class WeakReferenceDemo {
public static void main(String[] args) throws InterruptedException {
// 弱引用測試
WeakReference<byte[]> phantomReference = new WeakReference<>(
new byte[1024 * 1024 * 5]);
System.gc();
byte[] bytes = new byte[1024 * 1024 * 6];
}
}
同樣的設置jvm最大的10m內存
他們兩個的運行結果一致,在創建第二個大對象的時候,內存不足,GC則會回收之前的5m對象;
在網上找了很多答案,都沒有介紹到虛引用指向的對象具體被回收的時間;
後來看了仔細研讀了JDK8中對PhantomReference類的介紹,才逐漸看明白;
在收集者確定其指示物可能被回收之後排入隊列的Phantom參考對象。 幻像引用最常用於以比Java完成機制可能更靈活的方式安排事先清理操作。
如果垃圾收集器在某個時間點確定幻像引用的引用是phantom reachable ,那麼在那個時間或稍後的時間,它將引入引用。爲了確保可回收對象保持原樣,可能無法檢索幻像引用的引用:虛幻引用的get方法始終返回null 。
與軟弱引用不同,幻像引用在垃圾收集器排入隊列時不會自動清除。 通過幻影引用可訪問的對象將一直保持到所有這樣的引用被清除或者自身變得不可訪問。
仔細讀最後一句話,明確指出了他跟軟弱引用不同,即使入隊列了GC也不會自動清除,直到 所有這樣的引用被清除或者自身變得不可訪問
有點不好理解,畢竟是英文翻譯過來的。
結合代碼嘗試:
import java.lang.ref.PhantomReference;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
/**
* @Author: xiaoshijiu
* @Date: 2020/2/27
* @Description:
*/
public class PhantomReferenceDemo {
public static void main(String[] args) throws InterruptedException {
ReferenceQueue<byte[]> queue = new ReferenceQueue<>();
// 虛引用必須要和引用隊列一起使用,他的get方法永遠返回null
PhantomReference<byte[]> phantomReference = new PhantomReference<>(
new byte[1024 * 1024 * 5], queue);
System.out.println(queue.poll());
System.gc();
Thread.sleep(300L);
// 根據JDK8的api文檔介紹,將所有這樣的引用被清除或者自身變得不可訪問,GC纔會回收
Reference<? extends byte[]> poll = queue.poll();
System.out.println(poll);
poll = null;
phantomReference = null;
byte[] bytes = new byte[1024 * 1024 * 6];
}
}
加了幾行代碼,將原虛引用和出隊的虛引用都置爲null
最後成功:再次創建大對象,沒有報OOM
這樣我們就可以重新總結虛引用:GC時會入隊列,但是只有在所有這樣的引用被清除或者自身變得不可訪問纔會回收所指向的對象。
感悟
這麼細的知識,網上很難找的想要的答案,以後在遇到還是要好好讀讀JDK官方文檔的解析。