Android中緩存理解(一)

Java GarbageCollection(GC)

Java不能像C/C++那樣直接對(duì)內(nèi)存進(jìn)行操作(內(nèi)存分配和垃圾回收)韩容。

由于JVM會(huì)自動(dòng)回收(GC)险胰,Java程序員很難控制JVM的內(nèi)存回收,只能根據(jù)其原理去編寫代碼,提高程序性能。正是因?yàn)檫@樣的特性轧铁,導(dǎo)致Java程序員對(duì)內(nèi)存管理方面束手無策:

  • 垃圾回收并不會(huì)按照程序員的要求,隨時(shí)進(jìn)行GC旦棉。
  • 垃圾回收并不會(huì)及時(shí)的清理內(nèi)存齿风,盡管有時(shí)程序需要額外的內(nèi)存药薯。
  • 程序員不能對(duì)垃圾回收進(jìn)行控制。

根據(jù)垃圾回收的規(guī)律救斑,合理安排內(nèi)存童本,提高程序性能,這就要求必須徹底了解JVM的內(nèi)存管理機(jī)制脸候。

哪些對(duì)象需要GC

JVM運(yùn)行時(shí)數(shù)據(jù)區(qū)主要分為兩大塊:線程私有區(qū)和共享區(qū)穷娱。線程私有區(qū)(程序計(jì)數(shù)器、虛擬機(jī)棧运沦、本地方法區(qū))因其生命周期與線程同步泵额,它們會(huì)隨著線程的終結(jié)而自動(dòng)釋放內(nèi)存,所以只有共享區(qū)需要進(jìn)行GC携添。

如果共享區(qū)中的一個(gè)對(duì)象不存在任何引用時(shí)嫁盲,那么它可以被回收。

Java GC回收的堆內(nèi)存的對(duì)象烈掠,Java對(duì)象一般都是在堆中分配內(nèi)存羞秤。

GC時(shí)機(jī)

在共享區(qū)內(nèi)存的對(duì)象回收的條件是:沒有任何作用時(shí),就需要被回收左敌。而實(shí)例回收的時(shí)機(jī)取決于GC算法瘾蛋。

曾經(jīng)的GC算法是引用計(jì)數(shù)法,每一個(gè)對(duì)象都有一個(gè)引用計(jì)數(shù)器矫限。每被引用一次哺哼,計(jì)數(shù)器加1,失去引用奇唤,計(jì)數(shù)器減1幸斥。當(dāng)對(duì)象的計(jì)數(shù)器一段時(shí)間內(nèi)保持為0,那么認(rèn)為可以被回收咬扇。

但是這個(gè)算法有個(gè)明顯的缺陷:當(dāng)兩個(gè)對(duì)象相互引用,但是二者已經(jīng)沒有作用時(shí)廊勃,按照常規(guī)懈贺,應(yīng)該對(duì)其進(jìn)行垃圾回收,但是其相互引用坡垫,又不符合垃圾回收的條件梭灿,因此無法完美處理這塊內(nèi)存清理。

所以采用了根搜索算法冰悠,對(duì)象之間的引用構(gòu)建成一個(gè)樹堡妒,根元素是GC Roots對(duì)象。從根元素向下搜索溉卓,如果一個(gè)對(duì)象不能達(dá)到根元素皮迟,說明不再引用搬泥,即可被回收。

Java的引用

在JDK1.2后伏尼,引入了四中引用類型:強(qiáng)引用忿檩、軟引用、弱引用和虛引用爆阶,它們對(duì)于GC有著不同的意義燥透。

強(qiáng)引用
Object o = new Object();

強(qiáng)引用是指使用關(guān)鍵詞new創(chuàng)建一個(gè)實(shí)例,并將其賦值給引用類型變量引用辨图,就是給該實(shí)例添加強(qiáng)引用班套。

其特點(diǎn)是當(dāng)內(nèi)存不足時(shí),JVM寧可拋出OOM異常終止程序故河,也不會(huì)去回收強(qiáng)引用實(shí)例來釋放內(nèi)存吱韭。

如果希望強(qiáng)引用實(shí)例在適當(dāng)?shù)臅r(shí)機(jī)被回收需要進(jìn)行一定的弱化處理。

o = null;

一旦引用為null或者超出了對(duì)象的生命周期忧勿,則GC認(rèn)為該對(duì)象的引用不存在杉女,可以回收。

/**
* ArrayList.clear()源碼鸳吸,弱化強(qiáng)引用熏挎,使其元素被回收。
* 因?yàn)閑lementData是一個(gè)實(shí)例變量晌砾。
* 這里不是采用對(duì)elementData引用賦值null坎拐,是為了后續(xù)使用add()可繼續(xù)添加元素。
**/

public void clear() {  
        modCount++;  
        // Let gc do its work  
        for (int i = 0; i < size; i++)  
            elementData[i] = null;  
        size = 0;  
} 

在方法中养匈,如果有一個(gè)局部強(qiáng)引用變量去引用堆內(nèi)存中的對(duì)象哼勇,當(dāng)方法執(zhí)行完退出Java虛擬機(jī)棧后,引用也就不存在呕乎,該對(duì)象就有可能被回收积担。

類與對(duì)象的生命周期

Java類的完整生命周期是指一個(gè)字節(jié)碼文件從加載到卸載的全過程:加載->連接->初始化->使用->卸載。

類的加載統(tǒng)稱是類的加載猬仁,連接和初始化三個(gè)過程帝璧。

  1. 加載階段,找到需要加載的類并把類的信息加載到j(luò)vm的方法區(qū)中湿刽,然后在堆區(qū)中實(shí)例化一個(gè)java.lang.Class對(duì)象的烁,作為方法區(qū)中這個(gè)類的信息的入口;
  2. 連接階段诈闺,這個(gè)階段的主要任務(wù)就是做一些加載后的驗(yàn)證工作以及一些初始化前的準(zhǔn)備工作渴庆,可以細(xì)分為三個(gè)步驟:驗(yàn)證、準(zhǔn)備和解析;
  3. 初始化階段襟雷,只會(huì)初始化與類相關(guān)的靜態(tài)賦值語句和靜態(tài)語句刃滓,也就是有static關(guān)鍵字修飾的信息。主動(dòng)引用觸發(fā)初始化嗤军,主動(dòng)引用有:
    • 通過new關(guān)鍵字實(shí)例化對(duì)象注盈、讀取或設(shè)置類的靜態(tài)變量、調(diào)用類的靜態(tài)方法叙赚。
    • 通過反射方式執(zhí)行以上三種行為老客。
    • 初始化子類的時(shí)候,會(huì)觸發(fā)父類的初始化震叮。
    • 作為程序入口直接運(yùn)行時(shí)(也就是直接調(diào)用main方法)胧砰。
  4. 使用階段,類的使用包括主動(dòng)引用和被動(dòng)引用苇瓣,主動(dòng)引用會(huì)引起類的初始化尉间,而被動(dòng)引用不會(huì)引起類的初始化。被動(dòng)引用有:
    • 引用父類的靜態(tài)字段击罪,只會(huì)引起父類的初始化哲嘲,而不會(huì)引起子類的初始化。
    • 定義類數(shù)組媳禁,不會(huì)引起類的初始化眠副。
    • 引用類的常量,不會(huì)引起類的初始化竣稽。
  5. 卸載階段囱怕,在類使用完畢后,滿足以下所有條件毫别,jvm就會(huì)在方法區(qū)垃圾回收的時(shí)候?qū)︻愡M(jìn)行卸載(類的卸載過程其實(shí)就是在方法區(qū)中清空類信息)娃弓,至此java類的整個(gè)生命周期就結(jié)束了。
    • 該類所有的實(shí)例都已經(jīng)被回收岛宦,也就是java堆中不存在該類的任何實(shí)例台丛。
    • 加載該類的ClassLoader已經(jīng)被回收。
    • 該類對(duì)應(yīng)的java.lang.Class對(duì)象沒有任何地方被引用砾肺,無法在任何地方通過反射訪問該類的方法齐佳。

Java對(duì)象的生命周期是類生命周期一部分-使用階段的主動(dòng)引用(實(shí)例化對(duì)象)。

對(duì)象基本上在jvm的堆區(qū)中創(chuàng)建债沮。在創(chuàng)建對(duì)象之前,會(huì)觸發(fā)類加載本鸣。

當(dāng)類初始化完成后疫衩,根據(jù)類信息在堆區(qū)中實(shí)例化類對(duì)象:初始化非靜態(tài)變量、非靜態(tài)代碼以及默認(rèn)構(gòu)造方法荣德。

當(dāng)對(duì)象使用完之后會(huì)在合適的時(shí)候被jvm垃圾收集器回收闷煤。

參考自詳解java類的生命周期

軟引用
    String str = new String("abc");
    SoftReference<String> sr = new SoftReference<String>(str);
    str = null;

如果一個(gè)對(duì)象只具有軟引用時(shí)童芹,則當(dāng)內(nèi)存不夠時(shí),垃圾回收器就會(huì)回收它鲤拿。

一般軟引用用于實(shí)現(xiàn)內(nèi)存敏感的高速緩存假褪。

由于被回收的軟引用無法再給程序使用,所以使用前需要進(jìn)行判斷

    if(sr.get() != null) {
        str = sr.get();
    }else {
        str = new String("abc");
    }
弱引用
    String str=new String("abc");      
    WeakReference<String> abcWeakRef = new WeakReference<String>(str);  
    str=null;  

只具有弱引用的對(duì)象擁有更短的生命周期近顷,無論當(dāng)前內(nèi)存空間是否足夠生音,一旦被垃圾回收線程發(fā)現(xiàn),就會(huì)回收它窒升。但是因?yàn)槔厥站€程優(yōu)先級(jí)比較低缀遍,所以不一定很快就能發(fā)現(xiàn)它。

如果一個(gè)對(duì)象引用頻率不高且要求引用時(shí)快速饱须,但不想控制其生命周期域醇,使其不被回收,可以使用弱引用蓉媳。

虛引用

虛引用有以下幾個(gè)特點(diǎn):

  1. 與其他幾種引用不同的是譬挚,虛引用不會(huì)決定對(duì)象的生命周期;
  2. 如果一個(gè)對(duì)象僅持有虛引用酪呻,那么它就和沒有任何引用一樣减宣,在任何時(shí)候都可能被垃圾回收器回收;
  3. 虛引用必須和引用隊(duì)列一起使用号杠。

虛引用的主要作用是用來跟蹤對(duì)象垃圾回收的活動(dòng)蚪腋。當(dāng)Java回收一個(gè)對(duì)象的時(shí)候,如果發(fā)現(xiàn)他有虛引用姨蟋,會(huì)在回收對(duì)象之前將他的虛引用加入到與之關(guān)聯(lián)的引用隊(duì)列中屉凯。可以通過這個(gè)特性在一個(gè)對(duì)象被回收之前采取措施眼溶。

參考Java 7之基礎(chǔ) - 強(qiáng)引用悠砚、弱引用、軟引用堂飞、虛引用

如何GC

垃圾回收算法決定了回收方法灌旧,而Java內(nèi)存分為三類:新生代,舊生代和持久代绰筛,每一類的回收算法不同枢泰。

內(nèi)存分類

  1. 新生代,特點(diǎn)生命周期短铝噩,頻繁的創(chuàng)建和銷毀對(duì)象衡蚂。新建的對(duì)象都存儲(chǔ)在新生代。
  2. 舊生代,特點(diǎn)生命周期長毛甲。用于存放新生代中經(jīng)過多次回收仍然存活的對(duì)象年叮,如緩存對(duì)象。
  3. 持久代玻募,在Sun的HotSpot中指方法區(qū)只损,可能有些JVM沒有這塊內(nèi)存。主要存放常量和類的信息七咧。

常見的GC算法

  • 標(biāo)記回收算法(Mark and Sweep GC):從”GC Roots”集合開始跃惫,將內(nèi)存整個(gè)遍歷一次,保留所有可以被GC Roots直接或間接引用到的對(duì)象坑雅,而剩下的對(duì)象都當(dāng)作垃圾對(duì)待并回收辈挂,這個(gè)算法需要中斷進(jìn)程內(nèi)其它組件的執(zhí)行并且可能產(chǎn)生內(nèi)存碎片。
  • 復(fù)制算法(Copying):將現(xiàn)有的內(nèi)存空間分為兩快裹粤,每次只使用其中一塊终蒂,在垃圾回收時(shí)將正在使用的內(nèi)存中的存活對(duì)象復(fù)制到未被使用的內(nèi)存塊中,之后遥诉,清除正在使用的內(nèi)存塊中的所有對(duì)象拇泣,交換兩個(gè)內(nèi)存的角色,完成垃圾回收矮锈。
  • 標(biāo)記-壓縮算法(Mark-Compact) :先需要從根節(jié)點(diǎn)開始對(duì)所有可達(dá)對(duì)象做一次標(biāo)記霉翔,但之后,它并不簡單地清理未標(biāo)記的對(duì)象苞笨,而是將所有的存活對(duì)象壓縮到內(nèi)存的一端债朵。之后,清理邊界外所有的空間瀑凝。這種方法既避免了碎片的產(chǎn)生序芦,又不需要兩塊相同的內(nèi)存空間,因此粤咪,其性價(jià)比比較高谚中。

根據(jù)算法的特點(diǎn),新生代一般使用復(fù)制算法寥枝,由于其頻繁的回收對(duì)象宪塔,需要高效率的復(fù)制算法;而舊生代則采用標(biāo)記-壓縮算法囊拜。

引用自Android GC 那點(diǎn)事

常見的問題

關(guān)于內(nèi)存的問題主要有兩類:內(nèi)存溢出(OOM)和內(nèi)存泄漏某筐。

內(nèi)存溢出

在編寫代碼時(shí),聲明變量并且要求虛擬機(jī)分配內(nèi)存時(shí)冠跷,超出了系統(tǒng)限定的容量来吩,不能滿足敢辩,于是產(chǎn)生內(nèi)存溢出。

內(nèi)存泄漏

對(duì)象可以達(dá)到GC Roots弟疆,但是程序無法直接使用它,相當(dāng)于該對(duì)象無作用盗冷,但是又不符合回收條件無法回收內(nèi)存空間怠苔,導(dǎo)致內(nèi)存泄漏。

一旦這類無法分配的內(nèi)存積累仪糖,就會(huì)導(dǎo)致內(nèi)存溢出柑司,程序crash。

性能優(yōu)化

System.gc()

調(diào)用System.gc()表明Java虛擬機(jī)為了快速重用不用的對(duì)象所占用的內(nèi)存付出了努力锅劝。

當(dāng)控制權(quán)返回給調(diào)用者時(shí)攒驰,說明Java虛擬機(jī)已經(jīng)做出了最大的努力去回收那些被丟棄的對(duì)象所占用的內(nèi)存。

System.gc()只是建議JVM執(zhí)行GC故爵,但是GC的執(zhí)行與否完全是JVM決定的玻粪。

調(diào)用System.gc(),等價(jià)于Runtime.getRuntime().gc()诬垂。

Object.finalize()

單純的Java創(chuàng)建的實(shí)例都是在堆中分配內(nèi)存的劲室。如果使用JNI技術(shù),可能會(huì)在棧上分配內(nèi)存结窘。此時(shí)System.gc()無法對(duì)該區(qū)域無用內(nèi)存進(jìn)行回收很洋。

例如C語言的malloc()分配內(nèi)存,就需要使用Object.finalize()回收隧枫。由于使用了C函數(shù)malloc()分配內(nèi)存喉磁,需要使用free()進(jìn)行釋放內(nèi)存,而子類的需要去覆寫Object的finalize()官脓,實(shí)現(xiàn)調(diào)用free()來進(jìn)行棧內(nèi)存回收协怒。

一旦垃圾回收器確認(rèn)對(duì)象沒有引用,就會(huì)去調(diào)用該對(duì)象的finalize()确买。之后GC就會(huì)真正丟棄該對(duì)象釋放內(nèi)存斤讥。

但是在調(diào)用Object.finalize()之前,對(duì)象又存在引用湾趾,可以復(fù)活(即不被回收)芭商。所以一個(gè)對(duì)象不存在引用不代表一定會(huì)被GC回收。

注意如果finalize()沒有被覆寫去執(zhí)行其他清理或處理系統(tǒng)資源搀缠,無引用的對(duì)象也會(huì)被立刻回收铛楣。

System.runFinalization()

建議Java虛擬機(jī)去調(diào)用那些處于Finalizable階段(失去引用)對(duì)象的finalize(),前提是該方法沒有被調(diào)用過艺普。當(dāng)控制權(quán)返回給調(diào)用者時(shí)簸州,說明Java虛擬機(jī)已經(jīng)做出了最大的努力去完成調(diào)用finalize()鉴竭。

何時(shí)調(diào)用
  1. 所有對(duì)象被Garbage Collection時(shí)自動(dòng)調(diào)用,比如運(yùn)行System.gc()的時(shí)候。
  2. 程序退出時(shí)為每個(gè)對(duì)象調(diào)用一次finalize方法岸浑。
  3. 顯式的調(diào)用finalize方法搏存。

參考finalize() 和 system.gc() 的區(qū)別

編程習(xí)慣

  1. 避免在循環(huán)體內(nèi)創(chuàng)建實(shí)例,GC線程優(yōu)先級(jí)較低矢洲,不及時(shí)回收璧眠,很容易造成OOM,即使創(chuàng)建的實(shí)例占用內(nèi)存空間不大读虏。
  2. 盡量及時(shí)使對(duì)象符合垃圾回收標(biāo)準(zhǔn)责静,弱化強(qiáng)引用(例如ArrayList.clear()),使用合理的Reference存儲(chǔ)對(duì)象盖桥。
  3. 創(chuàng)建本地變量優(yōu)于實(shí)例變量灾螃,因?yàn)榫€程私有的Java虛擬機(jī)棧會(huì)隨著方法執(zhí)行完畢后釋放內(nèi)存。
  4. 等等還有許多揩徊。

參考

Java之美[從菜鳥到高手演變]之JVM內(nèi)存管理及垃圾回收

Android內(nèi)存泄漏小談

引用隊(duì)列ReferenceQueue

引用隊(duì)列腰鬼,在檢測(cè)到適當(dāng)?shù)目傻竭_(dá)性更改后,垃圾回收器將已注冊(cè)的引用對(duì)象添加到該隊(duì)列中靴拱。分別與軟垃喊,弱,虛引用搭配使用袜炕。

  • 軟引用本谜,當(dāng)SoftReference引用的對(duì)象被回收后,JVM會(huì)把這個(gè)軟引用加入到與之關(guān)聯(lián)的引用隊(duì)列中偎窘,一般實(shí)現(xiàn)內(nèi)存敏感的高速緩存乌助;
  • 弱引用,當(dāng)WeakReference引用的對(duì)象被回收后陌知,JVM會(huì)把這個(gè)弱引用加入到與之關(guān)聯(lián)的引用隊(duì)列中他托;
  • 虛引用,必須與引用隊(duì)列一起使用仆葡。當(dāng)垃圾回收器準(zhǔn)備回收一個(gè)對(duì)象時(shí)赏参,如果發(fā)現(xiàn)它還有虛引用,就會(huì)在回收對(duì)象的內(nèi)存之前沿盅,把這個(gè)虛引用加入到與之關(guān)聯(lián)的引用隊(duì)列中把篓。一般用于跟蹤對(duì)象被垃圾回收器回收的活動(dòng),這樣就可以在被回收之前拯救該對(duì)象腰涧。

使用軟引用構(gòu)建內(nèi)存敏感的高速緩存

原因

實(shí)際應(yīng)用中韧掩,總會(huì)有“回看”的操作,例如瀏覽器的回退等窖铡。那么這些信息如何處理這個(gè)需求以提高程序性能呢疗锐?一般有兩種:

  1. 內(nèi)存緩存坊谁,把信息對(duì)象保存在內(nèi)存中,對(duì)象的生命周期貫穿整個(gè)應(yīng)用滑臊;
  2. 重構(gòu)信息口芍,當(dāng)用戶查看其它信息時(shí),結(jié)束當(dāng)前信息對(duì)象引用简珠,讓其被GC阶界。一旦用戶回看時(shí),從其它介質(zhì)(磁盤文件聋庵,網(wǎng)絡(luò)資源,數(shù)據(jù)庫等等)獲取信息芙粱,重構(gòu)實(shí)例對(duì)象祭玉。

這兩種方法都有各自的缺陷:

方法一,由于大量對(duì)象存在春畔,導(dǎo)致大量內(nèi)存浪費(fèi)脱货,容易造成OOM;方法二律姨,由于垃圾回收線程的優(yōu)先級(jí)比較低振峻,所以對(duì)象可能無法及時(shí)回收,但是程序又無法使用它择份,只能通過重建對(duì)象來獲取信息扣孟。而訪問磁盤文件,網(wǎng)絡(luò)資源荣赶,數(shù)據(jù)庫等都是影響程序性能的重要因素凤价。

總結(jié),使用方案二拔创,使用完畢后結(jié)束引用利诺,在內(nèi)存緊張時(shí)可以回收這些信息對(duì)象。如果需要重新使用信息時(shí)剩燥,先去嘗試獲取未被回收的對(duì)象減少不必要的訪問慢逾,然后在考慮是否重建。

能夠像這樣去控制對(duì)象生命周期的引用-軟引用灭红。

對(duì)象的可及性

引用隊(duì)列是用于檢測(cè)對(duì)象的可及性變化侣滩,那對(duì)象的可及性是如何判定的?

樹形引用鏈.jpg
  1. 單條引用路徑可及性判斷:在這條路徑中比伏,最弱的一個(gè)引用決定對(duì)象的可及性胜卤。
  2. 多條引用路徑可及性判斷:幾條路徑中,最強(qiáng)的一條的引用決定對(duì)象的可及性赁项。

例如考慮對(duì)象5的可及性葛躏,由于路徑1-5中澈段,引用1是強(qiáng)引用,引用5是軟引用舰攒,所以此路徑對(duì)象5的可及性是軟引用败富;而路徑3-7中,引用3是強(qiáng)引用摩窃,引用7是弱引用兽叮,則此路徑對(duì)象5的可及性是弱引用。綜合兩條路徑猾愿,對(duì)象5的可及性就是軟引用鹦聪。

所以需要使一個(gè)對(duì)象只具備軟引用必須在實(shí)例化后弱化強(qiáng)引用。

Object o = new Object();
SoftReference sr = new SoftReference(o);//此時(shí)o的可及性的強(qiáng)可及
o = null;//此時(shí)o的可及性的軟可及
o = sr.get();//在回收之前重新獲取蒂秘,此時(shí)o的可及性是強(qiáng)引用

使用引用隊(duì)列清除失去引用對(duì)象的SoftReference

一旦JVM把SoftReference所引用的對(duì)象回收后泽本,這個(gè)SoftReference就沒有存在的價(jià)值。

因此需要采用一種清除機(jī)制姻僧,去清除無用的SoftReference规丽,避免大量的SoftReference導(dǎo)致內(nèi)存泄漏。

此時(shí)就要用到ReferenceQueue撇贺。

Object o = new Object();
ReferenceQueue queue = new ReferenceQueue();
SoftReference sr = new SoftReference(o, queue);
o = null;

//此時(shí)JVM內(nèi)存不足
System.gc();
SoftReference sfr = null;
if((sfr = queue.poll()) != null) {
    //clear softreference reference
    sr = null;
}

代碼分析:

  1. 創(chuàng)建軟引用時(shí)赌莺,將其綁定一個(gè)ReferenceQueue;
  2. 當(dāng)內(nèi)存不足時(shí)松嘶,JVM回收軟引用所引用的對(duì)象艘狭,并且將這個(gè)軟引用添加到ReferenceQueue;
  3. 調(diào)用隊(duì)列的poll()喘蟆,將隊(duì)列中頭部的軟引用返回缓升,如果隊(duì)列為空,返回null蕴轨;
  4. 此時(shí)終結(jié)強(qiáng)可及軟引用的生命周期港谊。

總結(jié)來說ReferenceQueue對(duì)于非強(qiáng)引用的作用就是檢測(cè)其可及性變化,以便針對(duì)這種變化提供處理橙弱。

實(shí)例

SoftReference回收的規(guī)則:JVM會(huì)在內(nèi)存不足時(shí)歧寺,回收SoftReference。但是會(huì)優(yōu)先回收長時(shí)間閑置不用的SoftReference棘脐,而保留剛創(chuàng)建或使用過的SoftReference斜筐。

利用這一特性,配合重獲實(shí)例方法蛀缝,來實(shí)現(xiàn)內(nèi)存敏感的高速緩存顷链。

首先建立一個(gè)Cache單利類,內(nèi)部有集合存儲(chǔ)SoftReference屈梁,ReferenceQueue嗤练。

Cache類中有一個(gè)緩存對(duì)象的公共方法榛了,首先清除隊(duì)列和集合中無用的SoftReference,然后生成軟引用加入集合

public void cacheModel(Object o) {
    cleanCache();
    SoftReference sr = new SoftReference(o, queue);
    collection.add(o);
}

清除方法

private void cleanCache() {
    SoftReference sr = null;
    while((sr = queue.poll()) != null) {
        collection.remove(sr);
    }
}

可以看到清除方法只要是利用ReferenceQueue.poll()煞抬,循環(huán)地去消除無用的SoftReference引用霜大,讓其變?yōu)榭苫厥铡M瑫r(shí)不要忘記對(duì)集合中同一個(gè)SoftReference做清除引用的操作革答,否則無法被回收战坤。

注意poll()不只是簡單的將SoftReference引用返回,其內(nèi)部實(shí)現(xiàn)真正做到了弱化對(duì)SoftReference的強(qiáng)引用

    @SuppressWarnings("unchecked")
    private Reference<? extends T> reallyPoll() {       /* Must hold lock */
        Reference<? extends T> r = head;
        if (r != null) {
            head = (r.next == r) ?
                null :
                r.next; // Unchecked due to the next field having a raw type in Reference
            r.queue = NULL;
            r.next = r;
            queueLength--;
            if (r instanceof FinalReference) {
                sun.misc.VM.addFinalRefCount(-1);
            }
            return r;
        }
        return null;
    }

可以看到ReferenceQueue的隊(duì)列形成是通過其成員變量head和Reference.next實(shí)現(xiàn)的残拐,所以當(dāng)poll時(shí)將head賦值為原先head的next或者為null(沒有其他元素時(shí))途茫。這樣一來隊(duì)列不再對(duì)SoftReference持有引用。

同理集合移除元素也是做了同樣的處理溪食。

注意這里不需要對(duì)cleanCache()中的sr進(jìn)行弱化慈省,因?yàn)樗蔷植孔兞浚S著方法的結(jié)束眠菇,自行釋放內(nèi)存。

Cache類還需有一個(gè)獲取緩存對(duì)象的方法

public Object getObject(int id) {
    SoftReference sr = null;
    Object o = null;
    if((sr = collection.get(id)) != null)
        o = sr.get();
    if(o == null) {
        o = new Object();
        cacheModel();
    }
    return o;
}

代碼中首先根據(jù)Cache類中的集合中的軟引用去獲取緩存的對(duì)象袱衷,然后去判斷獲取到的實(shí)例是否存在捎废。不存在的可能性有兩種:緩存的對(duì)象被JVM回收了或者從一開始就沒有緩存。一旦判斷為null致燥,那么對(duì)其進(jìn)行重構(gòu)登疗,并且返回。

最后為了完善Cache類的用途嫌蚤,可以為其添加清空的公共方法:

public void clean() {
    cleanCache();
    collection.clean();
}

參考

使用WeakReference 與 ReferenceQueue 簡單實(shí)現(xiàn)弱引用緩存

Java:對(duì)象的強(qiáng)辐益、軟、弱和虛引用

遺留問題

問題 參考
WeakReference實(shí)現(xiàn)非敏感數(shù)據(jù)緩存 Java:對(duì)象的強(qiáng)脱吱、軟智政、弱和虛引用
WeakHashMap的作用,與弱引用實(shí)現(xiàn)非敏感數(shù)據(jù)緩存有關(guān)嗎
弱引用實(shí)現(xiàn)的非敏感數(shù)據(jù)緩存與軟引用實(shí)現(xiàn)的敏感數(shù)據(jù)緩存有什么區(qū)別
String字面量存儲(chǔ)在方法區(qū)的運(yùn)行時(shí)常量區(qū)(String Pool)箱蝠,那么如何回收续捂?

由于String.intern()方法是native,所以字面量對(duì)象都是通過native方法在方法區(qū)運(yùn)行時(shí)常量區(qū)分配內(nèi)存宦搬。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末牙瓢,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子间校,更是在濱河造成了極大的恐慌矾克,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,214評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件憔足,死亡現(xiàn)場離奇詭異胁附,居然都是意外死亡酒繁,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門汉嗽,熙熙樓的掌柜王于貴愁眉苦臉地迎上來欲逃,“玉大人,你說我怎么就攤上這事饼暑∥任觯” “怎么了?”我有些...
    開封第一講書人閱讀 152,543評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵弓叛,是天一觀的道長彰居。 經(jīng)常有香客問我,道長撰筷,這世上最難降的妖魔是什么陈惰? 我笑而不...
    開封第一講書人閱讀 55,221評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮毕籽,結(jié)果婚禮上抬闯,老公的妹妹穿的比我還像新娘。我一直安慰自己关筒,他們只是感情好溶握,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評(píng)論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著蒸播,像睡著了一般睡榆。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上袍榆,一...
    開封第一講書人閱讀 49,007評(píng)論 1 284
  • 那天胀屿,我揣著相機(jī)與錄音,去河邊找鬼包雀。 笑死宿崭,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的馏艾。 我是一名探鬼主播劳曹,決...
    沈念sama閱讀 38,313評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼琅摩!你這毒婦竟也來了铁孵?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,956評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤房资,失蹤者是張志新(化名)和其女友劉穎蜕劝,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,441評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡岖沛,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評(píng)論 2 323
  • 正文 我和宋清朗相戀三年暑始,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片婴削。...
    茶點(diǎn)故事閱讀 38,018評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡廊镜,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出唉俗,到底是詐尸還是另有隱情嗤朴,我是刑警寧澤,帶...
    沈念sama閱讀 33,685評(píng)論 4 322
  • 正文 年R本政府宣布虫溜,位于F島的核電站雹姊,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏衡楞。R本人自食惡果不足惜吱雏,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望瘾境。 院中可真熱鬧歧杏,春花似錦、人聲如沸迷守。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽盒犹。三九已至,卻和暖如春眨业,著一層夾襖步出監(jiān)牢的瞬間急膀,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評(píng)論 1 261
  • 我被黑心中介騙來泰國打工龄捡, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留卓嫂,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,467評(píng)論 2 352
  • 正文 我出身青樓聘殖,卻偏偏與公主長得像晨雳,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子奸腺,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容