深入了解JAVA 軟引用
定義
軟引用是使用SoftReference創(chuàng)建的引用,強(qiáng)度弱于強(qiáng)引用,被其引用的對(duì)象在內(nèi)存不足的時(shí)候會(huì)被回收,不會(huì)產(chǎn)生內(nèi)存溢出。
說(shuō)明
軟引用,顧名思義就是比較“軟”一點(diǎn)的引用。
當(dāng)一個(gè)對(duì)象與GC Roots之間存在強(qiáng)引用時(shí),無(wú)論何時(shí)都不會(huì)被GC回收掉。如果一個(gè)對(duì)象與GC Roots之間沒(méi)有強(qiáng)引用與其關(guān)聯(lián)而存在軟引用關(guān)聯(lián)時(shí),那么垃圾回收器對(duì)它的態(tài)度就取決于內(nèi)存的緊張程度了。如果內(nèi)存空間足夠,垃圾回收器就不會(huì)回收這個(gè)對(duì)象,但如果內(nèi)存空間不足了,它就難逃被回收的厄運(yùn)。
如果一個(gè)對(duì)象與GC Roots之間不存在強(qiáng)引用,但是存在軟引用,則稱這個(gè)對(duì)象為軟可達(dá)(soft reachable)對(duì)象。
在垃圾回收器沒(méi)有回收它的時(shí)候,軟可達(dá)對(duì)象就像強(qiáng)可達(dá)對(duì)象一樣,可以被程序正常訪問(wèn)和使用,但是需要通過(guò)軟引用對(duì)象間接訪問(wèn),需要的話也能重新使用強(qiáng)引用將其關(guān)聯(lián)。所以軟引用適合用來(lái)做內(nèi)存敏感的高速緩存。
String s = new String("Frank"); // 創(chuàng)建強(qiáng)引用與String對(duì)象關(guān)聯(lián),現(xiàn)在該String對(duì)象為強(qiáng)可達(dá)狀態(tài) SoftReference<String> softRef = new SoftReference<String>(s); // 再創(chuàng)建一個(gè)軟引用關(guān)聯(lián)該對(duì)象 s = null; // 消除強(qiáng)引用,現(xiàn)在只剩下軟引用與其關(guān)聯(lián),該String對(duì)象為軟可達(dá)狀態(tài) s = softRef.get(); // 重新關(guān)聯(lián)上強(qiáng)引用
這里變量s持有對(duì)字符串對(duì)象的強(qiáng)引用,而softRef持有對(duì)該對(duì)象的軟引用,所以當(dāng)執(zhí)行s = null后,字符串對(duì)象就只剩下軟引用了,這時(shí)如果因?yàn)閮?nèi)存不足發(fā)生Full GC,就會(huì)把這個(gè)字符串對(duì)象回收掉。
注意,在垃圾回收器回收一個(gè)對(duì)象前,SoftReference類所提供的get方法會(huì)返回Java對(duì)象的強(qiáng)引用,一旦垃圾線程回收該對(duì)象之后,get方法將返回null。所以在獲取軟引用對(duì)象的代碼中,一定要先判斷返回是否為null,以免出現(xiàn)NullPointerException異常而導(dǎo)致應(yīng)用崩潰。
下面的代碼會(huì)讓s再次持有對(duì)象的強(qiáng)引用:
s = softRef.get();
如果在softRef指向的對(duì)象被回收前,用強(qiáng)引用指向該對(duì)象,那這個(gè)對(duì)象又會(huì)變成強(qiáng)可達(dá)。
來(lái)看一個(gè)使用SoftReference的栗子:
public class TestA { static class OOMClass{ private int[] oom = new int[1024 * 100];// 100KB } public static void main(String[] args) throws InterruptedException { ReferenceQueue<OOMClass> queue = new ReferenceQueue<>(); List<SoftReference> list = new ArrayList<>(); while(true){ for (int i = 0; i < 100; i++) { list.add(new SoftReference<OOMClass>(new OOMClass(), queue)); } Thread.sleep(500); } } }
注意,ReferenceQueue中聲明的類型為OOMClass,即與SoftReference引用的類型一致。
設(shè)置一下虛擬機(jī)參數(shù):
-verbose:gc -Xms4m -Xmx4m -Xmn2m
運(yùn)行結(jié)果:
[GC (Allocation Failure) 1017K->432K(3584K), 0.0017239 secs]
[GC (Allocation Failure) 1072K->472K(3584K), 0.0099237 secs]
[GC (Allocation Failure) 1323K->1296K(3584K), 0.0009528 secs]
[GC (Allocation Failure) 2114K->2136K(3584K), 0.0009951 secs]
[Full GC (Ergonomics) 2136K->1992K(3584K), 0.0040658 secs]
[Full GC (Ergonomics) 2807K->2791K(3584K), 0.0036280 secs]
[Full GC (Allocation Failure) 2791K->373K(3584K), 0.0032477 secs]
[Full GC (Ergonomics) 2786K->2773K(3584K), 0.0034554 secs]
[Full GC (Allocation Failure) 2773K->373K(3584K), 0.0032667 secs]
[Full GC (Ergonomics) 2798K->2775K(3584K), 0.0036231 secs]
[Full GC (Allocation Failure) 2775K->375K(3584K), 0.0055482 secs]
[Full GC (Ergonomics) 2799K->2776K(3584K), 0.0031358 secs]
...省略n次GC信息
在TestA中,我們使用死循環(huán)不斷的往list中添加新對(duì)象,如果是強(qiáng)引用,會(huì)很快因?yàn)閮?nèi)存不足而拋出OOM,因?yàn)檫@里的堆內(nèi)存大小設(shè)置為了4M,而一個(gè)對(duì)象就有100KB,一個(gè)循環(huán)添加100個(gè)對(duì)象,也就是差不多10M,顯然一個(gè)循環(huán)都跑不完就會(huì)內(nèi)存不足,而這里,因?yàn)槭褂玫氖擒浺?,所以JVM會(huì)在內(nèi)存不足的時(shí)候?qū)④浺没厥盏簟?/p>
[Full GC (Allocation Failure) 2791K->373K(3584K), 0.0032477 secs]
從這一條可以看出,在內(nèi)存不足發(fā)生Full GC時(shí),回收掉了大部分的軟引用指向的對(duì)象,釋放了大量的內(nèi)存。
因?yàn)檫@里新生代只分配了2M,所以很快就會(huì)發(fā)生GC,如果你的程序運(yùn)行沒(méi)有看到這個(gè)結(jié)果,請(qǐng)先確認(rèn)一下虛擬機(jī)參數(shù)是否設(shè)置正確,如果設(shè)置正確還是沒(méi)有看到,那么將循環(huán)次數(shù)由1000改為10000或者100000在試試看。
應(yīng)用場(chǎng)景
軟引用關(guān)聯(lián)的對(duì)象,只有在內(nèi)存不足的時(shí)候JVM才會(huì)回收該對(duì)象。這一點(diǎn)可以很好地用來(lái)解決OOM的問(wèn)題,并且這個(gè)特性很適合用來(lái)實(shí)現(xiàn)緩存:比如網(wǎng)頁(yè)緩存、圖片緩存等。
現(xiàn)在考慮這樣一個(gè)場(chǎng)景 ,在很多應(yīng)用中,都會(huì)出現(xiàn)大量的默認(rèn)圖片,比如說(shuō)QQ的默認(rèn)頭像,應(yīng)用內(nèi)的默認(rèn)圖標(biāo)等等,這些圖片很多地方會(huì)用到。
如果每次都去讀取圖片,由于讀取文件速度較慢,大量重復(fù)的讀取會(huì)導(dǎo)致性能下降。所以可以考慮將圖片緩存起來(lái),需要的時(shí)候直接從內(nèi)存中讀取。但是,由于圖片占用內(nèi)存空間比較大,緩存的圖片過(guò)多會(huì)占用比較多的內(nèi)存,就可能比較容易發(fā)生OOM。這時(shí)候,軟引用就派得上用場(chǎng)了。
注意,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)存泄漏。
ReferenceQueue就是用來(lái)保存這些需要被清理的引用對(duì)象的。軟引用可以和一個(gè)引用隊(duì)列(ReferenceQueue)聯(lián)合使用,如果軟引用所引用的對(duì)象被垃圾回收器回收,Java虛擬機(jī)就會(huì)把這個(gè)軟引用加入到與之關(guān)聯(lián)的引用隊(duì)列中。
下面用SoftReference來(lái)實(shí)現(xiàn)一個(gè)簡(jiǎn)單的緩存類:
public class SoftCache<T> { // 引用隊(duì)列 private ReferenceQueue<T> referenceQueue = new ReferenceQueue<>(); // 保存軟引用集合,在引用對(duì)象被回收后銷毀 private List<Reference<T>> list = new ArrayList<>(); // 添加緩存對(duì)象 public synchronized void add(T obj){ // 構(gòu)建軟引用 Reference<T> reference = new SoftReference<T>(obj, referenceQueue); // 加入列表中 list.add(reference); } // 獲取緩存對(duì)象 public synchronized T get(int index){ // 先對(duì)無(wú)效引用進(jìn)行清理 clear(); if (index < 0 || list.size() < index){ return null; } Reference<T> reference = list.get(index); return reference == null ? null : reference.get(); } public int size(){ return list.size(); } @SuppressWarnings("unchecked") private void clear(){ Reference<T> reference; while (null != (reference = (Reference<T>) referenceQueue.poll())){ list.remove(reference); } } }
然后測(cè)試一下這個(gè)緩存類:
public class SoftCacheTest { private static int num = 0; public static void main(String[] args){ SoftCache<OOMClass> softCache = new SoftCache<>(); for (int i = 0; i < 40; i++) { softCache.add(new OOMClass("OOM Obj-" + ++num)); } System.out.println(softCache.size()); for (int i = 0; i < softCache.size(); i++) { OOMClass obj = softCache.get(i); System.out.println(obj == null ? "null" : obj.name); } System.out.println(softCache.size()); } static class OOMClass{ private String name; private int[] oom = new int[1024 * 100];// 100KB public OOMClass(String name) { this.name = name; } } }
仍使用之前的虛擬機(jī)參數(shù):
-verbose:gc -Xms4m -Xmx4m -Xmn2m
運(yùn)行結(jié)果:
[GC (Allocation Failure) 1017K->432K(3584K), 0.0012236 secs]
[GC (Allocation Failure) 1117K->496K(3584K), 0.0016875 secs]
[GC (Allocation Failure) 1347K->1229K(3584K), 0.0015059 secs]
[GC (Allocation Failure) 2047K->2125K(3584K), 0.0018090 secs]
[Full GC (Ergonomics) 2125K->1994K(3584K), 0.0054759 secs]
[Full GC (Ergonomics) 2822K->2794K(3584K), 0.0023167 secs]
[Full GC (Allocation Failure) 2794K->376K(3584K), 0.0036056 secs]
[Full GC (Ergonomics) 2795K->2776K(3584K), 0.0042365 secs]
[Full GC (Allocation Failure) 2776K->376K(3584K), 0.0035122 secs]
[Full GC (Ergonomics) 2795K->2776K(3584K), 0.0054760 secs]
[Full GC (Allocation Failure) 2776K->376K(3584K), 0.0036965 secs]
[Full GC (Ergonomics) 2802K->2777K(3584K), 0.0044513 secs]
[Full GC (Allocation Failure) 2777K->376K(3584K), 0.0041400 secs]
[Full GC (Ergonomics) 2796K->2777K(3584K), 0.0025255 secs]
[Full GC (Allocation Failure) 2777K->376K(3584K), 0.0037690 secs]
[Full GC (Ergonomics) 2817K->2777K(3584K), 0.0037759 secs]
[Full GC (Allocation Failure) 2777K->377K(3584K), 0.0042416 secs]
緩存列表大?。?0
OOM Obj-37
OOM Obj-38
OOM Obj-39
OOM Obj-40
緩存列表大?。?
可以看到,緩存40個(gè)軟引用對(duì)象之后,如果一次性全部存儲(chǔ),顯然內(nèi)存大小無(wú)法滿足,所以在不斷創(chuàng)建軟引用對(duì)象的過(guò)程中,不斷發(fā)生GC來(lái)進(jìn)行垃圾回收,最終只有4個(gè)軟引用未被清理掉。
強(qiáng)引用與軟引用對(duì)比
沒(méi)有對(duì)比就沒(méi)有傷害,來(lái)將強(qiáng)引用和軟引用對(duì)比一下:
public class Test { static class OOMClass{ private int[] oom = new int[1024]; } public static void main(String[] args) { testStrongReference(); //testSoftReference(); } public static void testStrongReference(){ List<OOMClass> list = new ArrayList<>(); for (int i = 0; i < 1000; i++) { list.add(new OOMClass()); } } public static void testSoftReference(){ ReferenceQueue<OOMClass> referenceQueue = new ReferenceQueue<>(); List<SoftReference> list = new ArrayList<>(); for (int i = 0; i < 1000; i++) { OOMClass oomClass = new OOMClass(); list.add(new SoftReference(oomClass, referenceQueue)); oomClass = null; } } }
運(yùn)行testStrongReference方法的結(jié)果如下:
[GC (Allocation Failure) 1019K->384K(3584K), 0.0033595 secs]
[GC (Allocation Failure) 1406K->856K(3584K), 0.0013098 secs]
[GC (Allocation Failure) 1880K->1836K(3584K), 0.0014382 secs]
[Full GC (Ergonomics) 1836K->1756K(3584K), 0.0039761 secs]
[Full GC (Ergonomics) 2778K->2758K(3584K), 0.0021269 secs]
[Full GC (Ergonomics) 2779K->2770K(3584K), 0.0016329 secs]
[Full GC (Ergonomics) 2779K->2775K(3584K), 0.0023157 secs]
[Full GC (Ergonomics) 2775K->2775K(3584K), 0.0015927 secs]
[Full GC (Ergonomics) 3037K->3029K(3584K), 0.0025071 secs]
[Full GC (Ergonomics) 3067K->3065K(3584K), 0.0017529 secs]
[Full GC (Allocation Failure) 3065K->3047K(3584K), 0.0033445 secs]
[Full GC (Ergonomics) 3068K->3059K(3584K), 0.0016623 secs]
[Full GC (Ergonomics) 3070K->3068K(3584K), 0.0028357 secs]
[Full GC (Allocation Failure) 3068K->3068K(3584K), 0.0017616 secs]
java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid3352.hprof ...
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
Heap dump file created [3855956 bytes in 0.017 secs]
[Full GC (Ergonomics) 3071K->376K(3584K), 0.0032068 secs]
at reference.Test$OOMClass.<init>(Test.java:11)
at reference.Test.testStrongReference(Test.java:22)
at reference.Test.main(Test.java:15)Process finished with exit code 1
可以看到,很快就拋出了OOM,原因是Java heap space,也就是堆內(nèi)存不足。
如果運(yùn)行testSoftReference方法,將會(huì)得到如下結(jié)果:
[GC (Allocation Failure) 1019K->464K(3584K), 0.0019850 secs]
[GC (Allocation Failure) 1484K->844K(3584K), 0.0015920 secs]
[GC (Allocation Failure) 1868K->1860K(3584K), 0.0043236 secs]
[Full GC (Ergonomics) 1860K->1781K(3584K), 0.0044581 secs]
[Full GC (Ergonomics) 2802K->2754K(3584K), 0.0041726 secs]
[Full GC (Ergonomics) 2802K->2799K(3584K), 0.0031293 secs]
[Full GC (Ergonomics) 3023K->3023K(3584K), 0.0024830 secs]
[Full GC (Ergonomics) 3071K->3068K(3584K), 0.0035025 secs]
[Full GC (Allocation Failure) 3068K->405K(3584K), 0.0040672 secs]
[GC (Allocation Failure) 1512K->1567K(3584K), 0.0011170 secs]
[Full GC (Ergonomics) 1567K->1496K(3584K), 0.0048438 secs]
可以看到,并沒(méi)有拋出OOM,而是進(jìn)行多次了GC,可以明顯的看到這一條:
[Full GC (Allocation Failure) 3068K->405K(3584K), 0.0040672 secs]
當(dāng)內(nèi)存不足時(shí)進(jìn)行了一次Full GC,回收了大部分內(nèi)存空間,也就是將大部分軟引用指向的對(duì)象回收掉了。
小結(jié)
- 軟引用弱于強(qiáng)引用
- 軟引用指向的對(duì)象會(huì)在內(nèi)存不足時(shí)被垃圾回收清理掉
- JVM會(huì)優(yōu)先回收長(zhǎng)時(shí)間閑置不用的軟引用對(duì)象,對(duì)那些剛剛構(gòu)建的或剛剛使用過(guò)的軟引用對(duì)象會(huì)盡可能保留
- 軟引用可以有效的解決OOM問(wèn)題
- 軟引用適合用作非必須大對(duì)象的緩存
至此,本篇就告一段落了,這里只簡(jiǎn)單的介紹了軟引用的作用以及用法。其實(shí)軟引用并沒(méi)有這么好,它的使用有一些可能是致命的缺點(diǎn),如果想要更深入的了解軟引用的運(yùn)行原理以及軟引用到底是在何時(shí)進(jìn)行回收,又是如何進(jìn)行回收的話,可以查看翻閱后續(xù)的章節(jié)。
以上就是深入了解JAVA 軟引用的詳細(xì)內(nèi)容,更多關(guān)于JAVA 軟引用的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
通過(guò)weblogic API解析如何獲取weblogic中服務(wù)的IP和端口操作
這篇文章主要介紹了通過(guò)weblogic API解析如何獲取weblogic中服務(wù)的IP和端口操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-06-06TCP/IP協(xié)議中三次握手四次揮手的原理及流程分析
這篇文章主要介紹了TCP/IP協(xié)議中三次握手四次揮手的原理及流程分析,具有一定參考價(jià)值,需要的朋友可以了解下。2017-11-11詳解Spring-Boot集成Spring session并存入redis
這篇文章主要介紹了詳解Spring-Boot集成Spring session并存入redis,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-05-05如何使用Java模擬退火算法優(yōu)化Hash函數(shù)
為了解決局部最優(yōu)解問(wèn)題,1983年,Kirkpatrick等提出了模擬退火算法(SA)能有效的解決局部最優(yōu)解問(wèn)題。模擬退火算法包含兩個(gè)部分即Metropolis算法和退火過(guò)程。Metropolis算法就是如何在局部最優(yōu)解的情況下讓其跳出來(lái),是退火的基礎(chǔ)2021-06-06MyBatisPlus 主鍵策略的實(shí)現(xiàn)(4種)
MyBatis Plus 集成了多種主鍵策略,幫助用戶快速生成主鍵,本文主要介紹了MyBatisPlus主鍵策略的實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的可以了解一下2023-10-10