全面解析Java中的引用類型
如果一個(gè)內(nèi)存中的對(duì)象沒(méi)有任何引用的話,就說(shuō)明這個(gè)對(duì)象已經(jīng)不再被使用了,從而可以成為被垃圾回收的候選。不過(guò)由于垃圾回收器的運(yùn)行時(shí)間不確定,可被垃圾回收的對(duì)象的實(shí)際被回收時(shí)間是不確定的。對(duì)于一個(gè)對(duì)象來(lái)說(shuō),只要有引用的存在,它就會(huì)一直存在于內(nèi)存中。如果這樣的對(duì)象越來(lái)越多,超出了JVM中的內(nèi)存總數(shù),JVM就會(huì)拋出OutOfMemory錯(cuò)誤。雖然垃圾回收的具體運(yùn)行是由JVM來(lái)控制的,但是開(kāi)發(fā)人員仍然可以在一定程度上與垃圾回收器進(jìn)行交互,其目的在于更好的幫助垃圾回收器管理好應(yīng)用的內(nèi)存。這種交互方式就是使用JDK 1.2引入的java.lang.ref包。
1 強(qiáng)引用
強(qiáng)引用是使用最普遍的引用。如果一個(gè)對(duì)象具有強(qiáng)引用,那垃圾回收器絕不會(huì)回收它。當(dāng)內(nèi)存空間不足,Java虛擬機(jī)寧愿拋出OutOfMemoryError錯(cuò)誤,使程序異常終止,也不會(huì)靠隨意回收具有強(qiáng)引用的對(duì)象來(lái)解決內(nèi)存不足的問(wèn)題。
如Date date = new Date(),date就是一個(gè)對(duì)象的強(qiáng)引用。對(duì)象的強(qiáng)引用可以在程序中到處傳遞。很多情況下,會(huì)同時(shí)有多個(gè)引用指向同一個(gè)對(duì)象。強(qiáng)引用的存在限制了對(duì)象在內(nèi)存中的存活時(shí)間。假如對(duì)象A中包含了一個(gè)對(duì)象B的強(qiáng)引用,那么一般情況下,對(duì)象B的存活時(shí)間就不會(huì)短于對(duì)象A。如果對(duì)象A沒(méi)有顯式的把對(duì)象B的引用設(shè)為null的話,就只有當(dāng)對(duì)象A被垃圾回收之后,對(duì)象B才不再有引用指向它,才可能獲得被垃圾回收的機(jī)會(huì)。
實(shí)例代碼:
package com.skywang.java; public class StrongReferenceTest { public static void main(String[] args) { MyDate date = new MyDate(); System.gc(); } }
運(yùn)行結(jié)果:
<無(wú)任何輸出>
結(jié)果說(shuō)明:即使顯式調(diào)用了垃圾回收,但是用于date是強(qiáng)引用,date沒(méi)有被回收。
除了強(qiáng)引用之外,java.lang.ref包中提供了對(duì)一個(gè)對(duì)象的不同的引用方式。JVM的垃圾回收器對(duì)于不同類型的引用有不同的處理方式。
2 軟引用
如果一個(gè)對(duì)象只具有軟引用,則內(nèi)存空間足夠,垃圾回收器就不會(huì)回收它;如果內(nèi)存空間不足了,就會(huì)回收這些對(duì)象的內(nèi)存。只要垃圾回收器沒(méi)有回收它,該對(duì)象就可以被程序使用。軟引用可用來(lái)實(shí)現(xiàn)內(nèi)存敏感的高速緩存。
軟引用可以和一個(gè)引用隊(duì)列(ReferenceQueue)聯(lián)合使用,如果軟引用所引用的對(duì)象被垃圾回收器回收,Java虛擬機(jī)就會(huì)把這個(gè)軟引用加入到與之關(guān)聯(lián)的引用隊(duì)列中。
軟引用(soft reference)在強(qiáng)度上弱于強(qiáng)引用,通過(guò)類SoftReference來(lái)表示。它的作用是告訴垃圾回收器,程序中的哪些對(duì)象是不那么重要,當(dāng)內(nèi)存不足的時(shí)候是可以被暫時(shí)回收的。當(dāng)JVM中的內(nèi)存不足的時(shí)候,垃圾回收器會(huì)釋放那些只被軟引用所指向的對(duì)象。如果全部釋放完這些對(duì)象之后,內(nèi)存還不足,才會(huì)拋出OutOfMemory錯(cuò)誤。軟引用非常適合于創(chuàng)建緩存。當(dāng)系統(tǒng)內(nèi)存不足的時(shí)候,緩存中的內(nèi)容是可以被釋放的。比如考慮一個(gè)圖像編輯器的程序。該程序會(huì)把圖像文件的全部?jī)?nèi)容都讀取到內(nèi)存中,以方便進(jìn)行處理。而用戶也可以同時(shí)打開(kāi)多個(gè)文件。當(dāng)同時(shí)打開(kāi)的文件過(guò)多的時(shí)候,就可能造成內(nèi)存不足。如果使用軟引用來(lái)指向圖像文件內(nèi)容的話,垃圾回收器就可以在必要的時(shí)候回收掉這些內(nèi)存。
實(shí)例代碼:
package com.skywang.java; import java.lang.ref.SoftReference; public class SoftReferenceTest { public static void main(String[] args) { SoftReference ref = new SoftReference(new MyDate()); ReferenceTest.drainMemory(); } }
運(yùn)行結(jié)果:
<無(wú)任何輸出>
結(jié)果說(shuō)明:在內(nèi)存不足時(shí),軟引用被終止。軟引用被禁止時(shí),
SoftReference ref = new SoftReference(new MyDate()); ReferenceTest.drainMemory();
等價(jià)于
MyDate date = new MyDate(); // 由JVM決定運(yùn)行 if(JVM.內(nèi)存不足()) { date = null; System.gc(); }
3 弱引用
弱引用(weak reference)在強(qiáng)度上弱于軟引用,通過(guò)類WeakReference來(lái)表示。它的作用是引用一個(gè)對(duì)象,但是并不阻止該對(duì)象被回收。如果使用一個(gè)強(qiáng)引用的話,只要該引用存在,那么被引用的對(duì)象是不能被回收的。弱引用則沒(méi)有這個(gè)問(wèn)題。在垃圾回收器運(yùn)行的時(shí)候,如果一個(gè)對(duì)象的所有引用都是弱引用的話,該對(duì)象會(huì)被回收。弱引用的作用在于解決強(qiáng)引用所帶來(lái)的對(duì)象之間在存活時(shí)間上的耦合關(guān)系。弱引用最常見(jiàn)的用處是在集合類中,尤其在哈希表中。哈希表的接口允許使用任何Java對(duì)象作為鍵來(lái)使用。當(dāng)一個(gè)鍵值對(duì)被放入到哈希表中之后,哈希表對(duì)象本身就有了對(duì)這些鍵和值對(duì)象的引用。如果這種引用是強(qiáng)引用的話,那么只要哈希表對(duì)象本身還存活,其中所包含的鍵和值對(duì)象是不會(huì)被回收的。如果某個(gè)存活時(shí)間很長(zhǎng)的哈希表中包含的鍵值對(duì)很多,最終就有可能消耗掉JVM中全部的內(nèi)存。
對(duì)于這種情況的解決辦法就是使用弱引用來(lái)引用這些對(duì)象,這樣哈希表中的鍵和值對(duì)象都能被垃圾回收。Java中提供了WeakHashMap來(lái)滿足這一常見(jiàn)需求。
示例代碼:
package com.skywang.java; import java.lang.ref.WeakReference; public class WeakReferenceTest { public static void main(String[] args) { WeakReference ref = new WeakReference(new MyDate()); System.gc(); } }
運(yùn)行結(jié)果:
obj [Date: 1372142034360] is gc
結(jié)果說(shuō)明:在JVM垃圾回收運(yùn)行時(shí),弱引用被終止.
WeakReference ref = new WeakReference(new MyDate()); System.gc();
等同于:
MyDate date = new MyDate(); // 垃圾回收 if(JVM.內(nèi)存不足()) { date = null; System.gc(); }
弱引用與軟引用的區(qū)別在于:只具有弱引用的對(duì)象擁有更短暫的生命周期。在垃圾回收器線程掃描它所管轄的內(nèi)存區(qū)域的過(guò)程中,一旦發(fā)現(xiàn)了只具有弱引用的對(duì)象,不管當(dāng)前內(nèi)存空間足夠與否,都會(huì)回收它的內(nèi)存。不過(guò),由于垃圾回收器是一個(gè)優(yōu)先級(jí)很低的線程,因此不一定會(huì)很快發(fā)現(xiàn)那些只具有弱引用的對(duì)象。
弱引用可以和一個(gè)引用隊(duì)列(ReferenceQueue)聯(lián)合使用,如果弱引用所引用的對(duì)象被垃圾回收,Java虛擬機(jī)就會(huì)把這個(gè)弱引用加入到與之關(guān)聯(lián)的引用隊(duì)列中。
4 假象引用
又叫幽靈引用~在介紹幽靈引用之前,要先介紹Java提供的對(duì)象終止化機(jī)制(finalization)。在Object類里面有個(gè)finalize方法,其設(shè)計(jì)的初衷是在一個(gè)對(duì)象被真正回收之前,可以用來(lái)執(zhí)行一些清理的工作。因?yàn)镴ava并沒(méi)有提供類似C++的析構(gòu)函數(shù)一樣的機(jī)制,就通過(guò) finalize方法來(lái)實(shí)現(xiàn)。但是問(wèn)題在于垃圾回收器的運(yùn)行時(shí)間是不固定的,所以這些清理工作的實(shí)際運(yùn)行時(shí)間也是不能預(yù)知的。幽靈引用(phantom reference)可以解決這個(gè)問(wèn)題。在創(chuàng)建幽靈引用PhantomReference的時(shí)候必須要指定一個(gè)引用隊(duì)列。當(dāng)一個(gè)對(duì)象的finalize方法已經(jīng)被調(diào)用了之后,這個(gè)對(duì)象的幽靈引用會(huì)被加入到隊(duì)列中。通過(guò)檢查該隊(duì)列里面的內(nèi)容就知道一個(gè)對(duì)象是不是已經(jīng)準(zhǔn)備要被回收了。
幽靈引用及其隊(duì)列的使用情況并不多見(jiàn),主要用來(lái)實(shí)現(xiàn)比較精細(xì)的內(nèi)存使用控制,這對(duì)于移動(dòng)設(shè)備來(lái)說(shuō)是很有意義的。程序可以在確定一個(gè)對(duì)象要被回收之后,再申請(qǐng)內(nèi)存創(chuàng)建新的對(duì)象。通過(guò)這種方式可以使得程序所消耗的內(nèi)存維持在一個(gè)相對(duì)較低的數(shù)量。
比如下面的代碼給出了一個(gè)緩沖區(qū)的實(shí)現(xiàn)示例。
public class PhantomBuffer { private byte[] data = new byte[0]; private ReferenceQueue<byte[]> queue = new ReferenceQueue<byte[]>(); private PhantomReference<byte[]> ref = new PhantomReference<byte[]>(data, queue); public byte[] get(int size) { if (size <= 0) { throw new IllegalArgumentException("Wrong buffer size"); } if (data.length < size) { data = null; System.gc(); //強(qiáng)制運(yùn)行垃圾回收器 try { queue.remove(); //該方法會(huì)阻塞直到隊(duì)列非空 ref.clear(); //幽靈引用不會(huì)自動(dòng)清空,要手動(dòng)運(yùn)行 ref = null; data = new byte[size]; ref = new PhantomReference<byte[]>(data, queue); } catch (InterruptedException e) { e.printStackTrace(); } } return data; } }
在上面的代碼中,每次申請(qǐng)新的緩沖區(qū)的時(shí)候,都首先確保之前的緩沖區(qū)的字節(jié)數(shù)組已經(jīng)被成功回收。引用隊(duì)列的remove方法會(huì)阻塞直到新的幽靈引用被加入到隊(duì)列中。不過(guò)需要注意的是,這種做法會(huì)導(dǎo)致垃圾回收器被運(yùn)行的次數(shù)過(guò)多,可能會(huì)造成程序的吞吐量過(guò)低。
示例代碼:
package com.skywang.java; import java.lang.ref.ReferenceQueue; import java.lang.ref.PhantomReference; public class PhantomReferenceTest { public static void main(String[] args) { ReferenceQueue queue = new ReferenceQueue(); PhantomReference ref = new PhantomReference(new MyDate(), queue); System.gc(); } }
運(yùn)行結(jié)果:
obj [Date: 1372142282558] is gc
結(jié)果說(shuō)明:假象引用,在實(shí)例化后,就被終止了。
ReferenceQueue queue = new ReferenceQueue(); PhantomReference ref = new PhantomReference(new MyDate(), queue); System.gc();
等同于:
MyDate date = new MyDate(); date = null;
- 詳解Java的引用類型及使用場(chǎng)景
- java中的引用類型之強(qiáng)軟弱虛詳解
- 淺談Java 中的引用類型
- Java中值類型和引用類型的比較與問(wèn)題解決
- 詳解Java引用類型的參數(shù)也是值傳遞
- 解析java基本數(shù)據(jù)類型傳遞與引用傳遞區(qū)別
- Java中四種引用類型詳細(xì)介紹
- JAVA中值類型和引用類型的區(qū)別
- Java中int與integer的區(qū)別(基本數(shù)據(jù)類型與引用數(shù)據(jù)類型)
- Java/Android引用類型及其使用全面分析
- java的引用類型的詳細(xì)介紹
- 簡(jiǎn)述Java中的四種引用類型
相關(guān)文章
Java使用貪心算法解決電臺(tái)覆蓋問(wèn)題(示例詳解)
貪心算法是指在對(duì)問(wèn)題進(jìn)行求解時(shí),在每一步選擇中都采取最好或最優(yōu)的選擇,從而導(dǎo)致結(jié)果理想化,下面通過(guò)本文介紹下Java使用貪心算法解決電臺(tái)覆蓋問(wèn)題,需要的朋友可以參考下2022-04-04Java使用正則表達(dá)式判斷獨(dú)立字符的存在(代碼示例)
通過(guò)使用正則表達(dá)式,我們可以更加靈活地判斷字符串中是否包含特定的字符,并且可以控制匹配的條件,如獨(dú)立的字符,這為我們處理字符串提供了更多的選擇和功能,這篇文章主要介紹了Java使用正則表達(dá)式判斷獨(dú)立字符的存在,需要的朋友可以參考下2023-10-10SpringBoot常見(jiàn)錯(cuò)誤圖文總結(jié)
最近在使用idea+Springboot開(kāi)發(fā)項(xiàng)目中遇到一些問(wèn)題,這篇文章主要給大家介紹了關(guān)于SpringBoot常見(jiàn)錯(cuò)誤總結(jié)的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-06-06解決異常FileNotFoundException:class path resource找不到資源文件的問(wèn)題
今天小編就為大家分享一篇關(guān)于解決異常FileNotFoundException:class path resource找不到資源文件的問(wèn)題,小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧2018-12-12java 中HashMap實(shí)現(xiàn)原理深入理解
這篇文章主要介紹了java 中HashMap實(shí)現(xiàn)原理深入理解的相關(guān)資料,需要的朋友可以參考下2017-03-03Java開(kāi)發(fā)實(shí)現(xiàn)飛機(jī)大戰(zhàn)
這篇文章主要為大家詳細(xì)介紹了Java開(kāi)發(fā)實(shí)現(xiàn)飛機(jī)大戰(zhàn),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-05-05