我們知道Java語(yǔ)言中沒(méi)有指針,取而代之的是引用reference。Java中的引用又可以分為四種:強(qiáng)引用,弱引用(WeakReference),軟引用(SoftReference),虛引用(PhantomReference)。其中強(qiáng)引用,就是我們平時(shí)使用的最多的最普通的引用,虛引用一般我們是沒(méi)有機(jī)會(huì)使用到的。所以我們主要了解下 WeakReference 和 SoftReference(除了上面說(shuō)的四種引用之外,其實(shí)還有一種引用——原子引用AtomicReference,用于并發(fā)編程環(huán)境)。
1. 先上一段代碼:
public class ReferenceTest { public static void main(String[] args){ LinkedList<byte[]> list = new LinkedList<>(); for(int i=0; i<1024; i++){ list.add(new byte[1024*1024]); } }}上面的代碼會(huì)拋出:Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
堆內(nèi)存溢出。因?yàn)槲覀儾粩嗟脑诙焉戏峙湟粋€(gè) 1M 大小的 byte[]對(duì)象,并且將該引用加入到 list 中,循環(huán)1024次,需要占用 1G 的堆內(nèi)存,從而導(dǎo)致 heap space OutOfMemory.
2. 我們使用 WeekReference 對(duì)代碼進(jìn)行修改:
public class ReferenceTest { public static void main(String[] args) { long beginTime = System.nanoTime(); LinkedList<WeakReference<byte[]>> list = new LinkedList<>(); for (int i = 0; i < 1024; i++) { list.add(new WeakReference<>(new byte[1024 * 1024])); } long endTime = System.nanoTime(); System.out.PRintln(endTime - beginTime); }}輸出的結(jié)果:195947704 (0.19秒)
我們發(fā)現(xiàn)堆內(nèi)存溢出的錯(cuò)誤沒(méi)有了。這是什么原因呢。因?yàn)槲覀兪褂昧?弱引用WeekReference 來(lái)引用堆上的 1M 的byte[]對(duì)象,而弱引用WeekReference引用的對(duì)象,如果僅僅只被弱引用,而沒(méi)有被強(qiáng)引用的話,在下一次GC時(shí),就會(huì)回收該對(duì)象占用的內(nèi)存,所以不會(huì)內(nèi)存溢出。
3. 我們使用 SoftReference 對(duì)代碼進(jìn)行修改:
public class ReferenceTest { public static void main(String[] args) { long beginTime = System.nanoTime(); LinkedList<SoftReference<byte[]>> list = new LinkedList<>(); for (int i = 0; i < 1024; i++) { list.add(new SoftReference<>(new byte[1024 * 1024])); } long endTime = System.nanoTime(); System.out.println(endTime - beginTime); }}輸出結(jié)果:1499904286 (1.5秒)
我們發(fā)現(xiàn)堆內(nèi)存溢出的錯(cuò)誤也沒(méi)有了。因?yàn)槲覀兪褂昧?軟引用SoftReference 來(lái)引用堆上的 1M 的byte[]對(duì)象,而軟引用SoftReference引用的對(duì)象,如果僅僅只被軟引用,而沒(méi)有被強(qiáng)引用的話,在內(nèi)存空間不足時(shí),GC 就會(huì)回收該對(duì)象占用的內(nèi)存,所以不會(huì)內(nèi)存溢出。
但是我們注意到 采用WeekReference和采用SoftReference所花費(fèi)的時(shí)間,有接近10被的差距。原因應(yīng)該是,SoftReference只有在內(nèi)存空間不足時(shí),GC才會(huì)回收對(duì)象占用的空間,而這時(shí)進(jìn)行的是 full GC,full GC會(huì)導(dǎo)致 STW 程序暫停,所以花費(fèi)的時(shí)間過(guò)多。
4. 總結(jié)
強(qiáng)引用:只要堆上的對(duì)象,被至少一個(gè)強(qiáng)引用所指向,那么GC就不會(huì)回收該對(duì)象的內(nèi)存空間。
弱引用:只要堆上的對(duì)象僅僅只被弱引用所指向,不管當(dāng)前內(nèi)存空間是否足夠,下次GC都會(huì)回收對(duì)象的內(nèi)存空間。
軟引用:只要堆上的對(duì)象僅僅只被軟引用所指向,并且當(dāng)內(nèi)存空間不足時(shí),GC才會(huì)回收對(duì)象的內(nèi)存空間。
WeakReference 和 SoftReference一般使用在構(gòu)造一個(gè)緩存系統(tǒng),比如使用一個(gè)map來(lái)構(gòu)造。因?yàn)榫彺嫦到y(tǒng)是一個(gè)“全生命期”的對(duì)象,系統(tǒng)停止,緩存對(duì)象才會(huì)被銷毀,所以當(dāng)我們不斷的想緩存對(duì)象中添加對(duì)象時(shí),那么就會(huì)導(dǎo)致該緩存對(duì)象map所引用的對(duì)象越來(lái)越多,而因?yàn)槭菑?qiáng)引用,這些被放進(jìn)map緩存了的對(duì)象不能被GC鎖回收,那么就導(dǎo)致系統(tǒng)堆內(nèi)存占用會(huì)越來(lái)越大,從而最終導(dǎo)致內(nèi)存溢出。
那么此時(shí)我們就可以使用 WeakReference 或 SoftReference了,將強(qiáng)引用通過(guò)WeakReference 和 SoftReference 包裝之后,變成弱引用和軟引用,那么當(dāng)緩存中的對(duì)象,僅僅被緩存map鎖引用時(shí),那么分別在下次GC和內(nèi)存不足GC時(shí)就會(huì)回收這些對(duì)象占用的內(nèi)存。其實(shí)JDK給我們提供了一個(gè)專門的類:WeakHashMap ,弱引用的hashMap,所以構(gòu)造緩存系統(tǒng)是,我們可以考慮使用它。
其實(shí)這里引出了另外一個(gè)問(wèn)題,jdk中眾多的map,我們應(yīng)該如何進(jìn)行選擇:
HashMap
ConcurrentHashMap
TreeMap
WeakHashMap
LinkedHashMap
Collections.synchronizedMap
Hashtable
等等。我們?cè)谶x擇一個(gè)map時(shí),應(yīng)該好好的考慮下,那個(gè)更加適合我們的需求。
新聞熱點(diǎn)
疑難解答
圖片精選
網(wǎng)友關(guān)注