深入了解JAVA 虛引用
定義
虛引用是使用PhantomReference創(chuàng)建的引用,虛引用也稱為幽靈引用或者幻影引用,是所有引用類型中最弱的一個(gè)。一個(gè)對象是否有虛引用的存在,完全不會(huì)對其生命周期構(gòu)成影響,也無法通過虛引用獲得一個(gè)對象實(shí)例。
說明
虛引用,正如其名,對一個(gè)對象而言,這個(gè)引用形同虛設(shè),有和沒有一樣。
如果一個(gè)對象與GC Roots之間僅存在虛引用,則稱這個(gè)對象為虛可達(dá)(phantom reachable)對象。
當(dāng)試圖通過虛引用的get()方法取得強(qiáng)引用時(shí),總是會(huì)返回null,并且,虛引用必須和引用隊(duì)列一起使用。既然這么虛,那么它出現(xiàn)的意義何在??
別慌別慌,自然有它的用處。它的作用在于跟蹤垃圾回收過程,在對象被收集器回收時(shí)收到一個(gè)系統(tǒng)通知。 當(dāng)垃圾回收器準(zhǔn)備回收一個(gè)對象時(shí),如果發(fā)現(xiàn)它還有虛引用,就會(huì)在垃圾回收后,將這個(gè)虛引用加入引用隊(duì)列,在其關(guān)聯(lián)的虛引用出隊(duì)前,不會(huì)徹底銷毀該對象。 所以可以通過檢查引用隊(duì)列中是否有相應(yīng)的虛引用來判斷對象是否已經(jīng)被回收了。
如果一個(gè)對象沒有強(qiáng)引用和軟引用,對于垃圾回收器而言便是可以被清除的,在清除之前,會(huì)調(diào)用其finalize方法,如果一個(gè)對象已經(jīng)被調(diào)用過finalize方法但是還沒有被釋放,它就變成了一個(gè)虛可達(dá)對象。
與軟引用和弱引用不同,顯式使用虛引用可以阻止對象被清除,只有在程序中顯式或者隱式移除這個(gè)虛引用時(shí),這個(gè)已經(jīng)執(zhí)行過finalize方法的對象才會(huì)被清除。想要顯式的移除虛引用的話,只需要將其從引用隊(duì)列中取出然后扔掉(置為null)即可。
同樣來看一個(gè)栗子:
public class PhantomReferenceTest {
private static final List<Object> TEST_DATA = new LinkedList<>();
private static final ReferenceQueue<TestClass> QUEUE = new ReferenceQueue<>();
public static void main(String[] args) {
TestClass obj = new TestClass("Test");
PhantomReference<TestClass> phantomReference = new PhantomReference<>(obj, QUEUE);
// 該線程不斷讀取這個(gè)虛引用,并不斷往列表里插入數(shù)據(jù),以促使系統(tǒng)早點(diǎn)進(jìn)行GC
new Thread(() -> {
while (true) {
TEST_DATA.add(new byte[1024 * 100]);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
Thread.currentThread().interrupt();
}
System.out.println(phantomReference.get());
}
}).start();
// 這個(gè)線程不斷讀取引用隊(duì)列,當(dāng)弱引用指向的對象唄回收時(shí),該引用就會(huì)被加入到引用隊(duì)列中
new Thread(() -> {
while (true) {
Reference<? extends TestClass> poll = QUEUE.poll();
if (poll != null) {
System.out.println("--- 虛引用對象被jvm回收了 ---- " + poll);
System.out.println("--- 回收對象 ---- " + poll.get());
}
}
}).start();
obj = null;
try {
Thread.currentThread().join();
} catch (InterruptedException e) {
e.printStackTrace();
System.exit(1);
}
}
static class TestClass {
private String name;
public TestClass(String name) {
this.name = name;
}
@Override
public String toString() {
return "TestClass - " + name;
}
}
}
使用的虛擬機(jī)設(shè)置如下:
-verbose:gc -Xms4m -Xmx4m -Xmn2m
運(yùn)行結(jié)果如下:
[GC (Allocation Failure) 1024K->432K(3584K), 0.0113386 secs]
[GC (Allocation Failure) 1455K->520K(3584K), 0.0133610 secs]
[GC (Allocation Failure) 1544K->648K(3584K), 0.0008654 secs]
null
null
null
[GC (Allocation Failure) 1655K->973K(3584K), 0.0008111 secs]
null
...省略幾個(gè)null的輸出
[GC (Allocation Failure) 1980K->1997K(3584K), 0.0009289 secs]
[Full GC (Ergonomics) 1997K->1870K(3584K), 0.0048483 secs]
--- 弱引用對象被jvm回收了 ---- java.lang.ref.PhantomReference@74cbe23d
--- 回收對象 ---- null
null
...省略幾個(gè)null和幾次Full GC的輸出
[Full GC (Ergonomics) 2971K->2971K(3584K), 0.0024850 secs]
[Full GC (Allocation Failure) 2971K->2971K(3584K), 0.0022460 secs]
Exception in thread "Thread-0" java.lang.OutOfMemoryError: Java heap space
at weakhashmap.PhantomReferenceTest.lambda$main$0(PhantomReferenceTest.java:20)
at weakhashmap.PhantomReferenceTest$$Lambda$1/2065951873.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)
因?yàn)樵O(shè)置的虛擬機(jī)堆大小比較小,所以創(chuàng)建一個(gè)100k的對象時(shí)直接進(jìn)入了老年代,等到發(fā)生Full GC時(shí)才會(huì)被掃描然后回收。
適用場景
使用虛引用的目的就是為了得知對象被GC的時(shí)機(jī),所以可以利用虛引用來進(jìn)行銷毀前的一些操作,比如說資源釋放等。這個(gè)虛引用對于對象而言完全是無感知的,有沒有完全一樣,但是對于虛引用的使用者而言,就像是待觀察的對象的把脈線,可以通過它來觀察對象是否已經(jīng)被回收,從而進(jìn)行相應(yīng)的處理。
事實(shí)上,虛引用有一個(gè)很重要的用途就是用來做堆外內(nèi)存的釋放,DirectByteBuffer就是通過虛引用來實(shí)現(xiàn)堆外內(nèi)存的釋放的。
小結(jié)
- 虛引用是最弱的引用
- 虛引用對對象而言是無感知的,對象有虛引用跟沒有是完全一樣的
- 虛引用不會(huì)影響對象的生命周期
- 虛引用可以用來做為對象是否存活的監(jiān)控
以上就是詳解JAVA 虛引用的詳細(xì)內(nèi)容,更多關(guān)于JAVA 虛引用的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
詳解java連接mysql數(shù)據(jù)庫的五種方式
這篇文章主要介紹了詳解java連接mysql數(shù)據(jù)庫的五種方式,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-11-11
Java并發(fā)編程之ConcurrentLinkedQueue源碼詳解
今天帶小伙伴們學(xué)習(xí)一下Java并發(fā)編程之Java ConcurrentLinkedQueue源碼,本篇文章詳細(xì)分析了ConcurrentLinkedQueue源碼,有代碼示例,對正在學(xué)習(xí)java的小伙伴們很有幫助喲,需要的朋友可以參考下2021-05-05
SpringBoot中的靜態(tài)資源訪問的實(shí)現(xiàn)
這篇文章主要介紹了SpringBoot中的靜態(tài)資源訪問的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-09-09
基于spring-mvc.xml和application-context.xml的配置與深入理解
這篇文章主要介紹了spring-mvc.xml和application-context.xml的配置與深入解析,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-08-08
Java使用Tess4J實(shí)現(xiàn)圖像識(shí)別方式
這篇文章主要介紹了Java使用Tess4J實(shí)現(xiàn)圖像識(shí)別方式,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-10-10
Spring Boot 2.0多數(shù)據(jù)源配置方法實(shí)例詳解
這篇文章主要介紹了Spring Boot 2.0多數(shù)據(jù)源配置方法實(shí)例詳解,非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2018-09-09
spring boot 實(shí)現(xiàn)Minio分片上傳的步驟
分片上傳,就是將所要上傳的文件,按照一定的大小,將整個(gè)文件分隔成多個(gè)數(shù)據(jù)塊來進(jìn)行分別上傳,上傳完之后再由服務(wù)端對所有上傳的文件進(jìn)行匯總整合成原始的文件,本文給大家介紹spring boot 實(shí)現(xiàn)Minio分片上傳的步驟,感興趣的朋友跟隨小編一起看看吧2023-10-10

