引用
Java 虛擬機(jī)接管了所有的內(nèi)存分配與回收工作,極大地減少了程序員的工作量和錯(cuò)誤率。GC 在回收內(nèi)存時(shí),通常采用被稱為可達(dá)性分析的算法判斷一個(gè)對(duì)象是否可以回收。而在可達(dá)性分析中,對(duì)象的引用有著決定性的作用。在下圖中,GC 從 GC Roots 開始順著引用鏈往下尋找對(duì)象,發(fā)現(xiàn)當(dāng)前有引用的對(duì)象為object 1、object 2、object 3、object 4,而object 5、object 6、object 7雖然互相之間有引用但已經(jīng)無法從外部引用到。因此,圖中 object 1-4 為存活的對(duì)象,而 object 5-7 為可回收的對(duì)象。
雖然 GC 能夠完成垃圾收集工作,但是仍然無法避免 out of memory 。一方面在開發(fā)過程中需要注意不再使用的引用設(shè)為 null 來釋放引用的對(duì)象,另一方面也需要從對(duì)象引用的角度考慮使用合適的引用類型更好地管理對(duì)象內(nèi)存。
引用的類型
Java 有4種類型的引用:strong(強(qiáng)引用),soft(軟引用),weak(弱引用)和 phantom(虛引用)。
強(qiáng)引用:強(qiáng)引用是在 Java 中的普通引用。任何時(shí)候我們創(chuàng)建一個(gè)新的對(duì)象,默認(rèn)情況下創(chuàng)建一個(gè)強(qiáng)引用。例如類的靜態(tài)變量,從類被初始化之后便已經(jīng)分配內(nèi)存,作為強(qiáng)引用對(duì)象不能被 GC 回收,需要等待虛擬機(jī)退出或類被卸載才能釋放引用被 GC 回收。
弱引用:弱引用無法保證對(duì)象一定存活于內(nèi)存中,被弱引用關(guān)聯(lián)的對(duì)象只能生存到下一次垃圾收集發(fā)生之前。我們可以使用 WeakReference 類來實(shí)現(xiàn)弱引用。
軟引用:軟引用比弱引用稍強(qiáng)一點(diǎn),垃圾收集發(fā)生時(shí)弱引用一定會(huì)被回收,而軟引用會(huì)請(qǐng)求 GC 保留自己除非沒有其他選擇,可以理解為只在將要發(fā)生內(nèi)存溢出時(shí) GC 才會(huì)回收軟引用。我們可以使用 SoftReference 類來實(shí)現(xiàn)軟引用。
虛引用:一個(gè)對(duì)象是否有虛引用的存在,完全不會(huì)對(duì)其生存時(shí)間構(gòu)成影響,也無法通過虛引用來取得一個(gè)對(duì)象實(shí)例。為一個(gè)對(duì)象設(shè)置虛引用關(guān)聯(lián)的唯一目的就是能在這個(gè)對(duì)象被 GC 回收時(shí)收到一個(gè)系統(tǒng)通知。我們可以使用 PhantomReference 類來實(shí)現(xiàn)虛引用。當(dāng) GC 準(zhǔn)備回收一個(gè)對(duì)象時(shí),如果發(fā)現(xiàn)它還有虛引用,就會(huì)在回收對(duì)象的內(nèi)存之前,把這個(gè)虛引用加入到與之關(guān)聯(lián)的引用隊(duì)列中。開發(fā)者可以通過判斷引用隊(duì)列中是否包含對(duì)象來判斷對(duì)象是否即將被回收,可以在回收之前做些處理。
引用隊(duì)列:如果引用關(guān)聯(lián)了引用隊(duì)列,則 GC 回收對(duì)象內(nèi)存的時(shí)候會(huì)把引用加入到引用隊(duì)列中。當(dāng)引用隊(duì)列中包含引用時(shí),意味著引用指向的堆內(nèi)存中的對(duì)象被回收。
引用的應(yīng)用
構(gòu)建緩存
使用軟引用可以用于創(chuàng)建 Java 本地高速緩存,只要內(nèi)存仍然夠用緩存就不會(huì)被刪除,而一旦內(nèi)存緊張即將溢出時(shí),GC 會(huì)刪除部分緩存釋放內(nèi)存。例如,創(chuàng)建比較耗時(shí)影響性能的數(shù)據(jù)對(duì)象、一段時(shí)間內(nèi)可以重復(fù)使用的資源、不常變化的數(shù)據(jù)等,都可以使用軟引用構(gòu)建緩存,既能保證讀取性能,又不會(huì)導(dǎo)致內(nèi)存溢出。
WeakHashMap
WeakHashMap 是以弱引用鍵實(shí)現(xiàn)的哈希表。當(dāng) WeakHashMap 中的鍵不再被強(qiáng)引用使用時(shí),GC 下次回收垃圾時(shí)將回收此鍵。WeakHashMap 中的鍵被回收后,哈希表的條目也會(huì)被 GC 回收。因此,WeakHashMap 可以用于臨時(shí)存儲(chǔ)一些不需要長時(shí)間使用的對(duì)象,可以有效避免內(nèi)存溢出。
總結(jié)
Java 開發(fā)中最常用的引用是強(qiáng)引用,通過new創(chuàng)建對(duì)象得到強(qiáng)引用。強(qiáng)引用會(huì)阻止 GC 釋放對(duì)象內(nèi)存,長時(shí)間運(yùn)行容易導(dǎo)致內(nèi)存溢出。在開發(fā)中,對(duì)于強(qiáng)引用變量在使用完畢后應(yīng)把值設(shè)置為 null 來幫助 GC 進(jìn)行垃圾回收。在 Java 中還有另外三種引用類型,弱引用、軟引用和虛引用。弱引用和軟引用可以用于構(gòu)建緩存和避免內(nèi)存泄露,虛引用可以用于獲知對(duì)象將被回收的通知并進(jìn)行處理。
四種引用類型的對(duì)比如下: