引用與對(duì)象
每種編程語(yǔ)言都有自己操作內(nèi)存中元素的方式肝劲,例如在 C 和 C++ 里是通過(guò)指針诡壁,而在 Java 中則是通過(guò)“引用”麸澜。
在 Java 中一切都被視為了對(duì)象虹钮,但是我們操作的標(biāo)識(shí)符實(shí)際上是對(duì)象的一個(gè)引用(reference)坛梁。
//創(chuàng)建一個(gè)引用而姐,引用可以獨(dú)立存在,并不一定需要與一個(gè)對(duì)象關(guān)聯(lián)
String s;
復(fù)制代碼
通過(guò)將這個(gè)叫“引用”的標(biāo)識(shí)符指向某個(gè)對(duì)象划咐,之后便可以通過(guò)這個(gè)引用來(lái)實(shí)現(xiàn)操作對(duì)象了拴念。
String str = new String("abc");
System.out.println(str.toString());
復(fù)制代碼
在 JDK1.2 之前,Java中的定義很傳統(tǒng):如果 reference 類型的數(shù)據(jù)中存儲(chǔ)的數(shù)值代表的是另外一塊內(nèi)存的起始地址褐缠,就稱為這塊內(nèi)存代表著一個(gè)引用政鼠。
Java 中的垃圾回收機(jī)制在判斷是否回收某個(gè)對(duì)象的時(shí)候,都需要依據(jù)“引用”這個(gè)概念队魏。
在不同垃圾回收算法中公般,對(duì)引用的判斷方式有所不同:
- 引用計(jì)數(shù)法:為每個(gè)對(duì)象添加一個(gè)引用計(jì)數(shù)器,每當(dāng)有一個(gè)引用指向它時(shí)胡桨,計(jì)數(shù)器就加1官帘,當(dāng)引用失效時(shí),計(jì)數(shù)器就減1昧谊,當(dāng)計(jì)數(shù)器為0時(shí)刽虹,則認(rèn)為該對(duì)象可以被回收(目前在Java中已經(jīng)棄用這種方式了)。
- 可達(dá)性分析算法:從一個(gè)被稱為 GC Roots 的對(duì)象開(kāi)始向下搜索呢诬,如果一個(gè)對(duì)象到GC Roots沒(méi)有任何引用鏈相連時(shí)涌哲,則說(shuō)明此對(duì)象不可用。
JDK1.2 之前尚镰,一個(gè)對(duì)象只有“已被引用”和"未被引用"兩種狀態(tài)阀圾,這將無(wú)法描述某些特殊情況下的對(duì)象,比如狗唉,當(dāng)內(nèi)存充足時(shí)需要保留初烘,而內(nèi)存緊張時(shí)才需要被拋棄的一類對(duì)象。
四種引用類型
所以在 JDK.1.2 之后,Java 對(duì)引用的概念進(jìn)行了擴(kuò)充账月,將引用分為了:強(qiáng)引用(Strong Reference)综膀、軟引用(Soft Reference)、弱引用(Weak Reference)局齿、虛引用(Phantom Reference)4 種剧劝,這 4 種引用的強(qiáng)度依次減弱。
一抓歼,強(qiáng)引用
Java中默認(rèn)聲明的就是強(qiáng)引用讥此,比如:
Object obj = new Object(); //只要obj還指向Object對(duì)象,Object對(duì)象就不會(huì)被回收
obj = null; //手動(dòng)置null
復(fù)制代碼
只要強(qiáng)引用存在谣妻,垃圾回收器將永遠(yuǎn)不會(huì)回收被引用的對(duì)象萄喳,哪怕內(nèi)存不足時(shí),JVM也會(huì)直接拋出OutOfMemoryError蹋半,不會(huì)去回收他巨。如果想中斷強(qiáng)引用與對(duì)象之間的聯(lián)系,可以顯示的將強(qiáng)引用賦值為null减江,這樣一來(lái)染突,JVM就可以適時(shí)的回收對(duì)象了
二,軟引用
軟引用是用來(lái)描述一些非必需但仍有用的對(duì)象辈灼。在內(nèi)存足夠的時(shí)候份企,軟引用對(duì)象不會(huì)被回收,只有在內(nèi)存不足時(shí)巡莹,系統(tǒng)則會(huì)回收軟引用對(duì)象司志,如果回收了軟引用對(duì)象之后仍然沒(méi)有足夠的內(nèi)存,才會(huì)拋出內(nèi)存溢出異常降宅。這種特性常常被用來(lái)實(shí)現(xiàn)緩存技術(shù)骂远,比如網(wǎng)頁(yè)緩存,圖片緩存等腰根。
在 JDK1.2 之后吧史,用java.lang.ref.SoftReference類來(lái)表示軟引用。
下面以一個(gè)例子來(lái)進(jìn)一步說(shuō)明強(qiáng)引用和軟引用的區(qū)別:
在運(yùn)行下面的Java代碼之前唠雕,需要先配置參數(shù) -Xms2M -Xmx3M,將 JVM 的初始內(nèi)存設(shè)為2M吨述,最大可用內(nèi)存為 3M岩睁。
首先先來(lái)測(cè)試一下強(qiáng)引用,在限制了 JVM 內(nèi)存的前提下揣云,下面的代碼運(yùn)行正常
public class TestOOM {
public static void main(String[] args) {
testStrongReference();
}
private static void testStrongReference() {
// 當(dāng) new byte為 1M 時(shí)捕儒,程序運(yùn)行正常
byte[] buff = new byte[1024 * 1024 * 1];
}
}
復(fù)制代碼
但是如果我們將
byte[] buff = new byte[1024 * 1024 * 1];
復(fù)制代碼
替換為創(chuàng)建一個(gè)大小為 2M 的字節(jié)數(shù)組
byte[] buff = new byte[1024 * 1024 * 2];
復(fù)制代碼
則內(nèi)存不夠使用,程序直接報(bào)錯(cuò),強(qiáng)引用并不會(huì)被回收
接著來(lái)看一下軟引用會(huì)有什么不一樣刘莹,在下面的示例中連續(xù)創(chuàng)建了 10 個(gè)大小為 1M 的字節(jié)數(shù)組阎毅,并賦值給了軟引用,然后循環(huán)遍歷將這些對(duì)象打印出來(lái)点弯。
public class TestOOM {
private static List<Object> list = new ArrayList<>();
public static void main(String[] args) {
testSoftReference();
}
private static void testSoftReference() {
for (int i = 0; i < 10; i++) {
byte[] buff = new byte[1024 * 1024];
SoftReference<byte[]> sr = new SoftReference<>(buff);
list.add(sr);
}
System.gc(); //主動(dòng)通知垃圾回收
for(int i=0; i < list.size(); i++){
Object obj = ((SoftReference) list.get(i)).get();
System.out.println(obj);
}
}
}
復(fù)制代碼
打印結(jié)果:
我們發(fā)現(xiàn)無(wú)論循環(huán)創(chuàng)建多少個(gè)軟引用對(duì)象扇调,打印結(jié)果總是只有最后一個(gè)對(duì)象被保留,其他的obj全都被置空回收了抢肛。
這里就說(shuō)明了在內(nèi)存不足的情況下狼钮,軟引用將會(huì)被自動(dòng)回收。
值得注意的一點(diǎn) , 即使有 byte[] buff 引用指向?qū)ο? 且 buff 是一個(gè)strong reference, 但是 SoftReference sr 指向的對(duì)象仍然被回收了捡絮,這是因?yàn)镴ava的編譯器發(fā)現(xiàn)了在之后的代碼中, buff 已經(jīng)沒(méi)有被使用了, 所以自動(dòng)進(jìn)行了優(yōu)化熬芜。
如果我們將上面示例稍微修改一下:
private static void testSoftReference() {
byte[] buff = null;
for (int i = 0; i < 10; i++) {
buff = new byte[1024 * 1024];
SoftReference<byte[]> sr = new SoftReference<>(buff);
list.add(sr);
}
System.gc(); //主動(dòng)通知垃圾回收
for(int i=0; i < list.size(); i++){
Object obj = ((SoftReference) list.get(i)).get();
System.out.println(obj);
}
System.out.println("buff: " + buff.toString());
}
復(fù)制代碼
則 buff 會(huì)因?yàn)閺?qiáng)引用的存在,而無(wú)法被垃圾回收福稳,從而拋出OOM的錯(cuò)誤涎拉。
如果一個(gè)對(duì)象惟一剩下的引用是軟引用,那么該對(duì)象是軟可及的(softly reachable)的圆。垃圾收集器并不像其收集弱可及的對(duì)象一樣盡量地收集軟可及的對(duì)象鼓拧,相反,它只在真正 “需要” 內(nèi)存時(shí)才收集軟可及的對(duì)象略板。
三毁枯,弱引用
弱引用的引用強(qiáng)度比軟引用要更弱一些,無(wú)論內(nèi)存是否足夠叮称,只要 JVM 開(kāi)始進(jìn)行垃圾回收种玛,那些被弱引用關(guān)聯(lián)的對(duì)象都會(huì)被回收。在 JDK1.2 之后瓤檐,用 java.lang.ref.WeakReference 來(lái)表示弱引用赂韵。
我們以與軟引用同樣的方式來(lái)測(cè)試一下弱引用:
private static void testWeakReference() {
for (int i = 0; i < 10; i++) {
byte[] buff = new byte[1024 * 1024];
WeakReference<byte[]> sr = new WeakReference<>(buff);
list.add(sr);
}
System.gc(); //主動(dòng)通知垃圾回收
for(int i=0; i < list.size(); i++){
Object obj = ((WeakReference) list.get(i)).get();
System.out.println(obj);
}
}
復(fù)制代碼
打印結(jié)果:
可以發(fā)現(xiàn)所有被弱引用關(guān)聯(lián)的對(duì)象都被垃圾回收了。
四挠蛉,虛引用
虛引用是最弱的一種引用關(guān)系祭示,如果一個(gè)對(duì)象僅持有虛引用,那么它就和沒(méi)有任何引用一樣谴古,它隨時(shí)可能會(huì)被回收质涛,在 JDK1.2 之后,用 PhantomReference 類來(lái)表示掰担,通過(guò)查看這個(gè)類的源碼汇陆,發(fā)現(xiàn)它只有一個(gè)構(gòu)造函數(shù)和一個(gè) get() 方法,而且它的 get() 方法僅僅是返回一個(gè)null带饱,也就是說(shuō)將永遠(yuǎn)無(wú)法通過(guò)虛引用來(lái)獲取對(duì)象毡代,虛引用必須要和 ReferenceQueue 引用隊(duì)列一起使用阅羹。
public class PhantomReference<T> extends Reference<T> {
/**
* Returns this reference object's referent. Because the referent of a
* phantom reference is always inaccessible, this method always returns
* <code>null</code>.
*
* @return <code>null</code>
*/
public T get() {
return null;
}
public PhantomReference(T referent, ReferenceQueue<? super T> q) {
super(referent, q);
}
}
復(fù)制代碼
那么傳入它的構(gòu)造方法中的 ReferenceQueue 又是如何使用的呢?
五教寂,引用隊(duì)列(ReferenceQueue)
引用隊(duì)列可以與軟引用捏鱼、弱引用以及虛引用一起配合使用,當(dāng)垃圾回收器準(zhǔn)備回收一個(gè)對(duì)象時(shí)酪耕,如果發(fā)現(xiàn)它還有引用导梆,那么就會(huì)在回收對(duì)象之前,把這個(gè)引用加入到與之關(guān)聯(lián)的引用隊(duì)列中去因妇。程序可以通過(guò)判斷引用隊(duì)列中是否已經(jīng)加入了引用问潭,來(lái)判斷被引用的對(duì)象是否將要被垃圾回收,這樣就可以在對(duì)象被回收之前采取一些必要的措施婚被。
與軟引用狡忙、弱引用不同,虛引用必須和引用隊(duì)列一起使用址芯。
下面是博主剪輯的視頻資料 可能與文章無(wú)關(guān) 希望大家可以支持一下哦灾茁!謝謝大家支持!
UP主:我只需一小時(shí)帶你玩轉(zhuǎn)Git&Github B友直呼:菜雞這不是有手就行谷炸?
【面試必備】阿里資深架構(gòu)師詳解 2021最新 Java秒殺系統(tǒng)高性能高并發(fā)實(shí)戰(zhàn)項(xiàng)目
Java零基礎(chǔ)小白看完我這個(gè)系列視頻都可以自己做實(shí)戰(zhàn)項(xiàng)目啦北专!拿捏呢!
2021最新版lntellij IDEA 安裝旬陡、配置拓颓、環(huán)境變量教學(xué)
2021最新算法訓(xùn)練營(yíng):左神帶你 爆刷LeetCode算法(1000題) 進(jìn)大廠的必修算法課程!
B站首發(fā) 花費(fèi)12980巨資購(gòu)買的 微服務(wù)SpringCloud Alibaba全集