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è)過程帝璧。
- 加載階段,找到需要加載的類并把類的信息加載到j(luò)vm的方法區(qū)中湿刽,然后在堆區(qū)中實(shí)例化一個(gè)java.lang.Class對(duì)象的烁,作為方法區(qū)中這個(gè)類的信息的入口;
- 連接階段诈闺,這個(gè)階段的主要任務(wù)就是做一些加載后的驗(yàn)證工作以及一些初始化前的準(zhǔn)備工作渴庆,可以細(xì)分為三個(gè)步驟:驗(yàn)證、準(zhǔn)備和解析;
- 初始化階段襟雷,只會(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方法)胧砰。
- 使用階段,類的使用包括主動(dòng)引用和被動(dòng)引用苇瓣,主動(dòng)引用會(huì)引起類的初始化尉间,而被動(dòng)引用不會(huì)引起類的初始化。被動(dòng)引用有:
- 引用父類的靜態(tài)字段击罪,只會(huì)引起父類的初始化哲嘲,而不會(huì)引起子類的初始化。
- 定義類數(shù)組媳禁,不會(huì)引起類的初始化眠副。
- 引用類的常量,不會(huì)引起類的初始化竣稽。
- 卸載階段囱怕,在類使用完畢后,滿足以下所有條件毫别,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):
- 與其他幾種引用不同的是譬挚,虛引用不會(huì)決定對(duì)象的生命周期;
- 如果一個(gè)對(duì)象僅持有虛引用酪呻,那么它就和沒有任何引用一樣减宣,在任何時(shí)候都可能被垃圾回收器回收;
- 虛引用必須和引用隊(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)存分類
- 新生代,特點(diǎn)生命周期短铝噩,頻繁的創(chuàng)建和銷毀對(duì)象衡蚂。新建的對(duì)象都存儲(chǔ)在新生代。
- 舊生代,特點(diǎn)生命周期長毛甲。用于存放新生代中經(jīng)過多次回收仍然存活的對(duì)象年叮,如緩存對(duì)象。
- 持久代玻募,在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)記-壓縮算法囊拜。
常見的問題
關(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)用
- 所有對(duì)象被Garbage Collection時(shí)自動(dòng)調(diào)用,比如運(yùn)行System.gc()的時(shí)候。
- 程序退出時(shí)為每個(gè)對(duì)象調(diào)用一次finalize方法岸浑。
- 顯式的調(diào)用finalize方法搏存。
編程習(xí)慣
- 避免在循環(huán)體內(nèi)創(chuàng)建實(shí)例,GC線程優(yōu)先級(jí)較低矢洲,不及時(shí)回收璧眠,很容易造成OOM,即使創(chuàng)建的實(shí)例占用內(nèi)存空間不大读虏。
- 盡量及時(shí)使對(duì)象符合垃圾回收標(biāo)準(zhǔn)责静,弱化強(qiáng)引用(例如ArrayList.clear()),使用合理的Reference存儲(chǔ)對(duì)象盖桥。
- 創(chuàng)建本地變量優(yōu)于實(shí)例變量灾螃,因?yàn)榫€程私有的Java虛擬機(jī)棧會(huì)隨著方法執(zhí)行完畢后釋放內(nèi)存。
- 等等還有許多揩徊。
參考
Java之美[從菜鳥到高手演變]之JVM內(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è)需求以提高程序性能呢疗锐?一般有兩種:
- 內(nèi)存緩存坊谁,把信息對(duì)象保存在內(nèi)存中,對(duì)象的生命周期貫穿整個(gè)應(yīng)用滑臊;
- 重構(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ì)象的可及性是如何判定的?
- 單條引用路徑可及性判斷:在這條路徑中比伏,最弱的一個(gè)引用決定對(duì)象的可及性胜卤。
- 多條引用路徑可及性判斷:幾條路徑中,最強(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;
}
代碼分析:
- 創(chuàng)建軟引用時(shí)赌莺,將其綁定一個(gè)ReferenceQueue;
- 當(dāng)內(nèi)存不足時(shí)松嘶,JVM回收軟引用所引用的對(duì)象艘狭,并且將這個(gè)軟引用添加到ReferenceQueue;
- 調(diào)用隊(duì)列的poll()喘蟆,將隊(duì)列中頭部的軟引用返回缓升,如果隊(duì)列為空,返回null蕴轨;
- 此時(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)存宦搬。