摘要:虛引用與軟引用和弱引用的一個(gè)區(qū)別在于虛引用必須和引用隊(duì)列聯(lián)合使用。
本文已同步至個(gè)人博客liaosi"s blog
在Java中是由JVM負(fù)責(zé)內(nèi)存的分配和回收,這是它的優(yōu)點(diǎn)(簡(jiǎn)化編程者的工作,不需要像C語(yǔ)言那樣去手動(dòng)操作內(nèi)存),但同時(shí)也是它的缺點(diǎn)(不夠靈活,垃圾回收對(duì)于編程者來(lái)說(shuō)是不可控的)。
在JDK1.2以前,如果一個(gè)對(duì)象不被任何變量引用,則程序無(wú)法再次使用這個(gè)對(duì)象,這個(gè)對(duì)象最終會(huì)被GC(GabageCollection:垃圾回收)。但是如果之后可能還會(huì)用到這個(gè)對(duì)象,就只能去新建一個(gè)了,這其實(shí)就降低了JVM性能,沒(méi)有達(dá)到最大的優(yōu)化策略。
因此,從JDK1.2開(kāi)始,提供了四種類(lèi)型的引用:強(qiáng)引用(StrongReference)、軟引用(SoftReference)、弱引用(WeakReference)和虛引用(PhantomReference)。主要有兩個(gè)目的:
可以在代碼中決定某些對(duì)象的生命周期;
優(yōu)化JVM的垃圾回收機(jī)制。
關(guān)于GC什么是 GC(GabageCollection)?
GC通常是運(yùn)行在一個(gè)獨(dú)立的、優(yōu)先級(jí)比較低的線(xiàn)程中,實(shí)時(shí)監(jiān)測(cè)并釋放“無(wú)效”的內(nèi)存。
什么是“無(wú)效"的內(nèi)存單元?
一般GC采用引用計(jì)數(shù)法來(lái)判斷一個(gè)內(nèi)存單元(一個(gè)變量)是否是無(wú)效的內(nèi)存。
引用計(jì)數(shù)法(引用計(jì)數(shù)法只是GC中一種常用的方法,還會(huì)用到年代方法等)是指一個(gè)變量或一塊內(nèi)存當(dāng)前被引用的次數(shù),如果引用次數(shù)為0,則表示這個(gè)變量或這塊內(nèi)存未被引用,因此GC“有可能”去釋放它 ,為什么說(shuō)有可能?首先GC運(yùn)行在一個(gè)獨(dú)立的、優(yōu)先級(jí)比較低的線(xiàn)程中,其次GC回收的具體工作也是比較復(fù)雜的,比如說(shuō)需要釋放大量?jī)?nèi)存的時(shí)候,而CPU資源又相對(duì)緊張,GC可能會(huì)選擇性地釋放一些內(nèi)存資源,具體回收方法取決于GC內(nèi)部的算法。
強(qiáng)引用是最普遍的引用,如果一個(gè)對(duì)象具有強(qiáng)引用,垃圾回收器不會(huì)回收該對(duì)象,當(dāng)內(nèi)存空間不足時(shí),JVM 寧愿拋出 OutOfMemoryError異常;只有當(dāng)這個(gè)對(duì)象沒(méi)有被引用時(shí),才有可能會(huì)被回收。
package com.lzumetal.jvmtest; import java.util.ArrayList; import java.util.List; public class StrongReferenceTest { static class BigObject { private Byte[] bytes = new Byte[1024 * 1024]; } public static void main(String[] args) { Listlist = new ArrayList<>(); while (true) { BigObject obj = new BigObject(); list.add(obj); } } }
BigObject obj = new BigObject()創(chuàng)建的這個(gè)對(duì)象時(shí)就是強(qiáng)引用,上面的main方法最終將拋出OOM異常:
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space at com.lzumetal.jvm.StrongReferenceTest$BigObject.軟引用(StrongReferenceTest.java:9) at com.lzumetal.jvm.StrongReferenceTest.main(StrongReferenceTest.java:16)
如果一個(gè)對(duì)象只具有軟引用,則
當(dāng)內(nèi)存空間足夠,垃圾回收器就不會(huì)回收它。
當(dāng)內(nèi)存空間不足了,就會(huì)回收該對(duì)象。
JVM會(huì)優(yōu)先回收長(zhǎng)時(shí)間閑置不用的軟引用的對(duì)象,對(duì)那些剛剛構(gòu)建的或剛剛使用過(guò)的“新”軟引用對(duì)象會(huì)盡可能保留。
如果回收完還沒(méi)有足夠的內(nèi)存,才會(huì)拋出內(nèi)存溢出異常。只要垃圾回收器沒(méi)有回收它,該對(duì)象就可以被程序使用。
軟引用是用來(lái)描述一些有用但并不是必需的對(duì)象,適合用來(lái)實(shí)現(xiàn)緩存(比如瀏覽器的‘后退’按鈕使用的緩存),內(nèi)存空間充足的時(shí)候?qū)?shù)據(jù)緩存在內(nèi)存中,如果空間不足了就將其回收掉。
軟引用在Java中用java.lang.ref.SoftReference類(lèi)來(lái)表示。為了方便測(cè)試,在下面這個(gè)示例中我設(shè)置了JVM的內(nèi)存為8M,在IDEA的Run——>EditConfigiratons中設(shè)置參數(shù):-Xms8m -Xmx8m -XX:+PrintGCDetails
代碼:
package com.lzumetal.jvmtest; import java.lang.ref.SoftReference; public class SoftReferenceTest { static class Person { private String name; private Byte[] bytes = new Byte[1024 * 1024]; public Person(String name) { this.name = name; } } public static void main(String[] args) throws InterruptedException { Person person = new Person("張三"); SoftReferencesoftReference = new SoftReference<>(person); person = null; //去掉強(qiáng)引用,new Person("張三")的這個(gè)對(duì)象就只有軟引用了 System.gc(); Thread.sleep(1000); System.err.println("軟引用的對(duì)象 ------->" + softReference.get()); } }
運(yùn)行main方法,控制臺(tái)輸出:
[GC (Allocation Failure) [PSYoungGen: 1536K->504K(2048K)] 1536K->748K(7680K), 0.0118019 secs] [Times: user=0.08 sys=0.00, real=0.01 secs] [GC (System.gc()) [PSYoungGen: 1005K->496K(2048K)] 5346K->4868K(7680K), 0.0025626 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [Full GC (System.gc()) [PSYoungGen: 496K->0K(2048K)] [ParOldGen: 4372K->4773K(5632K)] 4868K->4773K(7680K), [Metaspace: 3466K->3466K(1056768K)], 0.0083134 secs] [Times: user=0.01 sys=0.00, real=0.01 secs] 軟引用的對(duì)象 ------->com.lzumetal.jvmtest.SoftReferenceTest$Person@6d6f6e28 Heap PSYoungGen total 2048K, used 45K [0x00000000ffd80000, 0x0000000100000000, 0x0000000100000000) eden space 1536K, 2% used [0x00000000ffd80000,0x00000000ffd8b7b8,0x00000000fff00000) from space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000) to space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000) ParOldGen total 5632K, used 4773K [0x00000000ff800000, 0x00000000ffd80000, 0x00000000ffd80000) object space 5632K, 84% used [0x00000000ff800000,0x00000000ffca9498,0x00000000ffd80000) Metaspace used 3474K, capacity 4500K, committed 4864K, reserved 1056768K class space used 382K, capacity 388K, committed 512K, reserved 1048576K
雖然調(diào)用System.gc()后JVM并不一定會(huì)立刻進(jìn)行GC操作,但從上面這段輸出可以看到JVM確實(shí)進(jìn)行了GC,但是軟引用的對(duì)象并沒(méi)有被回收掉,說(shuō)明現(xiàn)在內(nèi)存空間還足夠,JVM暫時(shí)還不會(huì)回收軟引用的對(duì)象。
把main方法改成如下:
public static void main(String[] args) throws InterruptedException { Person person = new Person("張三"); SoftReferencesoftReference = new SoftReference<>(person); person = null;//去掉強(qiáng)引用,new Person("張三")的這個(gè)對(duì)象就只有軟引用了 Person anotherPerson = new Person("李四"); Thread.sleep(1000); System.err.println("軟引用的對(duì)象 ------->" + softReference.get()); }
因?yàn)檫@里JVM內(nèi)存只有8M,沒(méi)有足夠的空間同時(shí)保留兩個(gè)Person對(duì)象(我已經(jīng)測(cè)試過(guò)了:new兩個(gè)強(qiáng)引用的Person對(duì)象就會(huì)報(bào)OOM),所以當(dāng)我再new Person("李四")時(shí),也是會(huì)觸發(fā)JVM的GC的,同時(shí)因?yàn)榍懊娴?b>new Person("張三")只有軟引用了,它會(huì)被回收掉。
[GC (Allocation Failure) [PSYoungGen: 1536K->504K(2048K)] 1536K->664K(7680K), 0.0009884 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [GC (Allocation Failure) [PSYoungGen: 1006K->504K(2048K)] 5262K->4848K(7680K), 0.0077414 secs] [Times: user=0.06 sys=0.00, real=0.01 secs] [GC (Allocation Failure) [PSYoungGen: 504K->504K(2048K)] 4848K->4872K(7680K), 0.0017661 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [Full GC (Allocation Failure) [PSYoungGen: 504K->0K(2048K)] [ParOldGen: 4368K->4773K(5632K)] 4872K->4773K(7680K), [Metaspace: 3465K->3465K(1056768K)], 0.0201011 secs] [Times: user=0.08 sys=0.00, real=0.02 secs] [GC (Allocation Failure) [PSYoungGen: 0K->0K(2048K)] 4773K->4773K(7680K), 0.0039905 secs] [Times: user=0.06 sys=0.00, real=0.00 secs] [Full GC (Allocation Failure) [PSYoungGen: 0K->0K(2048K)] [ParOldGen: 4773K->659K(5632K)] 4773K->659K(7680K), [Metaspace: 3465K->3465K(1056768K)], 0.0103549 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 軟引用的對(duì)象 ------->null Heap PSYoungGen total 2048K, used 45K [0x00000000ffd80000, 0x0000000100000000, 0x0000000100000000) eden space 1536K, 2% used [0x00000000ffd80000,0x00000000ffd8b7b8,0x00000000fff00000) from space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000) to space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000) ParOldGen total 5632K, used 4755K [0x00000000ff800000, 0x00000000ffd80000, 0x00000000ffd80000) object space 5632K, 84% used [0x00000000ff800000,0x00000000ffca4c80,0x00000000ffd80000) Metaspace used 3473K, capacity 4500K, committed 4864K, reserved 1056768K class space used 382K, capacity 388K, committed 512K, reserved 1048576KReferenceQueue
SoftReference對(duì)象是用來(lái)保存軟引用,但它同時(shí)也是一個(gè)Java對(duì)象。所以,當(dāng)軟可及對(duì)象被回收之后,雖然這個(gè)SoftReference對(duì)象的get()方法返回null,但SoftReference對(duì)象本身并不是null,而此時(shí)這個(gè)SoftReference對(duì)象已經(jīng)不再具有存在的價(jià)值,需要一個(gè)適當(dāng)?shù)那宄龣C(jī)制,避免大量SoftReference對(duì)象帶來(lái)的內(nèi)存泄漏。
在java.lang.ref包里還提供了ReferenceQueue。如果在創(chuàng)建SoftReference對(duì)象的時(shí)候,使用了一個(gè)ReferenceQueue對(duì)象作為參數(shù)提供給SoftReference的構(gòu)造方法,如:
Person person = new Person("張三"); ReferenceQueuequeue = new ReferenceQueue<>(); SoftReference softReference = new SoftReference (person, queue);
在SoftReference所軟引用的Person對(duì)象被垃圾回收時(shí),JVM會(huì)先將softReference對(duì)象添加到ReferenceQueue這個(gè)隊(duì)列中。當(dāng)我們調(diào)用ReferenceQueue的poll()方法,如果這個(gè)隊(duì)列中不是空隊(duì)列,那么將返回并移除前面添加的那個(gè)Reference對(duì)象。
還是上面的那個(gè)例子,測(cè)試代碼:
public static void main(String[] args) throws InterruptedException { Person person = new Person("張三"); ReferenceQueuequeue = new ReferenceQueue<>(); SoftReference softReference = new SoftReference (person, queue); person = null;//去掉強(qiáng)引用,new Person("張三")的這個(gè)對(duì)象就只有軟引用了 Person anotherPerson = new Person("李四"); Thread.sleep(1000); System.err.println("軟引用的對(duì)象 ------->" + softReference.get()); Reference softPollRef = queue.poll(); if (softPollRef != null) { System.err.println("SoftReference對(duì)象中保存的軟引用對(duì)象已經(jīng)被GC,準(zhǔn)備清理SoftReference對(duì)象"); //清理softReference } }
控制臺(tái)輸出:
[GC (Allocation Failure) [PSYoungGen: 1536K->504K(2048K)] 1536K->728K(7680K), 0.0022378 secs] [Times: user=0.03 sys=0.05, real=0.00 secs] [GC (Allocation Failure) [PSYoungGen: 1036K->504K(2048K)] 5356K->4840K(7680K), 0.0027540 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [GC (Allocation Failure) [PSYoungGen: 504K->504K(2048K)] 4840K->4840K(7680K), 0.0048557 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] [Full GC (Allocation Failure) [PSYoungGen: 504K->0K(2048K)] [ParOldGen: 4336K->4774K(5632K)] 4840K->4774K(7680K), [Metaspace: 3468K->3468K(1056768K)], 0.0087802 secs] [Times: user=0.11 sys=0.00, real=0.01 secs] [GC (Allocation Failure) [PSYoungGen: 0K->0K(2048K)] 4774K->4774K(7680K), 0.0005462 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [Full GC (Allocation Failure) [PSYoungGen: 0K->0K(2048K)] [ParOldGen: 4774K->659K(5632K)] 4774K->659K(7680K), [Metaspace: 3468K->3468K(1056768K)], 0.0104794 secs] [Times: user=0.05 sys=0.02, real=0.01 secs] 軟引用的對(duì)象 ------->null SoftReference對(duì)象中保存的軟引用對(duì)象已經(jīng)被GC,準(zhǔn)備清理SoftReference對(duì)象 Heap PSYoungGen total 2048K, used 45K [0x00000000ffd80000, 0x0000000100000000, 0x0000000100000000) eden space 1536K, 2% used [0x00000000ffd80000,0x00000000ffd8b7b8,0x00000000fff00000) from space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000) to space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000) ParOldGen total 5632K, used 4755K [0x00000000ff800000, 0x00000000ffd80000, 0x00000000ffd80000) object space 5632K, 84% used [0x00000000ff800000,0x00000000ffca4d70,0x00000000ffd80000) Metaspace used 3476K, capacity 4500K, committed 4864K, reserved 1056768K class space used 382K, capacity 388K, committed 512K, reserved 1048576K弱引用
弱引用與軟引用的區(qū)別在于:只具有弱引用的對(duì)象擁有更短暫的生命周期,它只能生存到下一次垃圾收集發(fā)生之前。當(dāng)垃圾回收器掃描到只具有弱引用的對(duì)象時(shí),無(wú)論當(dāng)前內(nèi)存空間是否足夠,都會(huì)回收它。不過(guò),由于垃圾回收器是一個(gè)優(yōu)先級(jí)很低的線(xiàn)程,因此不一定會(huì)很快發(fā)現(xiàn)那些只具有弱引用的對(duì)象。
弱引用也可以和一個(gè)引用隊(duì)列(ReferenceQueue)聯(lián)合使用。
使用場(chǎng)景:一個(gè)對(duì)象只是偶爾使用,希望在使用時(shí)能隨時(shí)獲取,但也不想影響對(duì)該對(duì)象的垃圾收集,則可以考慮使用弱引用來(lái)指向該對(duì)象。
參考上面的代碼示例,測(cè)試弱引用:
public static void main(String[] args) throws InterruptedException { Person person = new Person("張三"); ReferenceQueue虛引用queue = new ReferenceQueue<>(); WeakReference weakReference = new WeakReference (person, queue); person = null;//去掉強(qiáng)引用,new Person("張三")的這個(gè)對(duì)象就只有軟引用了 System.gc(); Thread.sleep(1000); System.err.println("弱引用的對(duì)象 ------->" + weakReference.get()); Reference weakPollRef = queue.poll(); //poll()方法是有延遲的 if (weakPollRef != null) { System.err.println("WeakReference對(duì)象中保存的弱引用對(duì)象已經(jīng)被GC,下一步需要清理該Reference對(duì)象"); //清理softReference } else { System.err.println("WeakReference對(duì)象中保存的軟引用對(duì)象還沒(méi)有被GC,或者被GC了但是獲得對(duì)列中的引用對(duì)象出現(xiàn)延遲"); } }
與其他三種引用都不同,虛引用并不會(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ì)列中。
Object object = new Object(); ReferenceQueue queue = new ReferenceQueue (); PhantomReference pr = new PhantomReference (object, queue);
程序可以通過(guò)判斷引用隊(duì)列中是 否已經(jīng)加入了虛引用,來(lái)了解被引用的對(duì)象是否將要被垃圾回收。程序如果發(fā)現(xiàn)某個(gè)虛引用已經(jīng)被加入到引用隊(duì)列,那么就可以在所引用的對(duì)象的內(nèi)存被回收之前采取必要的行動(dòng)。
在實(shí)際程序設(shè)計(jì)中一般很少使用弱引用與虛引用,使用軟引用的情況較多,這是因?yàn)檐浺每梢约铀貸VM對(duì)垃圾內(nèi)存的回收速度,可以維護(hù)系統(tǒng)的運(yùn)行安全,防止內(nèi)存溢出(OutOfMemory)等問(wèn)題的產(chǎn)生。
本文代碼已上傳至我的GitHub
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/71198.html
摘要:簡(jiǎn)單來(lái)說(shuō)就是引用和引用隊(duì)列關(guān)聯(lián)起來(lái)引用的構(gòu)造函數(shù)傳入隊(duì)列,然后引用被回收的時(shí)候會(huì)被添加到隊(duì)列中,然后使用方法可以返回引用。 引語(yǔ): ????我們知道java相比C,C++中沒(méi)有令人頭痛的指針,但是卻有和指針作用相似的引用對(duì)象(Reference),就是常說(shuō)的引用,比如,Object obj = new Object();這個(gè)obj就是引用,它指向的是真正的對(duì)象Object的地址,不過(guò)今...
摘要:在之后,對(duì)引用的概念進(jìn)行了擴(kuò)充,將引用分為強(qiáng)引用軟引用弱引用虛引用種,這種引用強(qiáng)度依次逐漸減弱。軟引用是用來(lái)描述一些還有用但并非必需的對(duì)象。虛引用也稱(chēng)為幽靈引用或者幻影引用,它是最弱的一種引用關(guān)系。 以下內(nèi)容摘自《深入理解Java虛擬機(jī) JVM高級(jí)特性與最佳實(shí)踐》第2版,強(qiáng)烈推薦沒(méi)有看過(guò)的同學(xué)閱讀,讀完的感覺(jué)就是原來(lái)學(xué)的都是些什么瘠薄東西(╯‵□′)╯︵┴─┴ 在JDK1.2以前,Ja...
摘要:如果想中斷強(qiáng)引用和某個(gè)對(duì)象之間的關(guān)聯(lián),可以顯式地將引用賦值為,這樣一來(lái)的話(huà),在合適的時(shí)間就會(huì)回收該對(duì)象。不過(guò)由于垃圾回收器是一個(gè)優(yōu)先級(jí)較低的線(xiàn)程,所以并不一定能迅速發(fā)現(xiàn)弱引用對(duì)象。 強(qiáng)引用,軟引用,弱引用,虛引用。不同的引用類(lèi)型主要體現(xiàn)在GC上: △強(qiáng)引用:如果一個(gè)對(duì)象具有強(qiáng)引用,它就不會(huì)被垃圾回收器回收。即使當(dāng)前內(nèi)存空間不足,JVM也不會(huì)回收它,而是拋出 OutOfMemoryErr...
閱讀 3723·2021-10-12 10:11
閱讀 1992·2019-08-30 15:53
閱讀 1597·2019-08-30 13:15
閱讀 2311·2019-08-30 11:25
閱讀 1808·2019-08-29 11:24
閱讀 1657·2019-08-26 13:53
閱讀 3530·2019-08-26 13:22
閱讀 1773·2019-08-26 10:24