1.內(nèi)存溢出 out of memory
是指程序在申請(qǐng)內(nèi)存時(shí),沒(méi)有足夠的內(nèi)存空間供其使用汤求,出現(xiàn)out of memory末贾;比如申請(qǐng)了一個(gè)integer,但給它存了long才能存下的數(shù),那就是內(nèi)存溢出
2.內(nèi)存泄漏 memory leak
是指程序在申請(qǐng)內(nèi)存后,無(wú)法釋放已申請(qǐng)的內(nèi)存空間纤勒,一次內(nèi)存泄露危害可以忽略,但內(nèi)存泄露堆積后果很?chē)?yán)重隆檀,無(wú)論多少內(nèi)存,都會(huì)被占光摇天。Memory Leak會(huì)最終會(huì)導(dǎo)致Out of Memory粹湃。
常見(jiàn)的內(nèi)存泄漏分為4類(lèi):
- 常發(fā)性?xún)?nèi)存泄漏。發(fā)生內(nèi)存泄漏的代碼會(huì)被多次執(zhí)行到泉坐,每次被執(zhí)行的時(shí)候都會(huì)導(dǎo)致一塊內(nèi)存泄漏为鳄。
- 偶發(fā)性?xún)?nèi)存泄漏。發(fā)生內(nèi)存泄漏的代碼只有在某些特定環(huán)境或操作過(guò)程下才會(huì)發(fā)生腕让。常發(fā)性和偶發(fā)性是相對(duì)的孤钦。對(duì)于特定的環(huán)境,偶發(fā)性的也許就變成了常發(fā)性的纯丸。所以測(cè)試環(huán)境和測(cè)試方法對(duì)檢測(cè)內(nèi)存泄漏至關(guān)重要搔扁。
- 一次性?xún)?nèi)存泄漏盗棵。發(fā)生內(nèi)存泄漏的代碼只會(huì)被執(zhí)行一次,或者由于算法上的缺陷,導(dǎo)致總會(huì)有一塊僅且一塊內(nèi)存發(fā)生泄漏唱捣。比如,在類(lèi)的構(gòu)造函數(shù)中分配內(nèi)存滥酥,在析構(gòu)函數(shù)中卻沒(méi)有釋放該內(nèi)存剩岳,所以?xún)?nèi)存泄漏只會(huì)發(fā)生一次。
- 隱式內(nèi)存泄漏仇矾。程序在運(yùn)行過(guò)程中不停的分配內(nèi)存庸蔼,但是直到結(jié)束的時(shí)候才釋放內(nèi)存。嚴(yán)格的說(shuō)這里并沒(méi)有發(fā)生內(nèi)存泄漏贮匕,因?yàn)樽罱K程序釋放了所有申請(qǐng)的內(nèi)存朱嘴。但是對(duì)于一個(gè)服務(wù)器程序,需要運(yùn)行幾天粗合,幾周甚至幾個(gè)月萍嬉,不及時(shí)釋放內(nèi)存也可能導(dǎo)致最終耗盡系統(tǒng)的所有內(nèi)存。所以隙疚,我們稱(chēng)這類(lèi)內(nèi)存泄漏為隱式內(nèi)存泄漏壤追。
防止內(nèi)存泄漏的主要方法是“引用”:
對(duì)象的引用主要分為4個(gè)級(jí)別的:
強(qiáng)引用:GC一般不會(huì)回收具有強(qiáng)引用的對(duì)象。
java中的引用供屉,類(lèi)似于C++的指針行冰。通過(guò)引用,可以對(duì)堆中的對(duì)象進(jìn)行操作伶丐。在某個(gè)函數(shù)中悼做,當(dāng)創(chuàng)建了一個(gè)對(duì)象,該對(duì)象被分配在堆中哗魂,通過(guò)這個(gè)對(duì)象的引用才能對(duì)這個(gè)對(duì)象進(jìn)行操作肛走。
聲明一個(gè)強(qiáng)引用
假設(shè)以上代碼是在方法內(nèi)運(yùn)行的,那么局部變量str將被分配在椔急穑空間上朽色,而對(duì)象StringBuffer實(shí)例邻吞,被分配在堆空間中。局部變量str指向StringBuffer實(shí)例所在的堆空間葫男,通過(guò)str可以操作該實(shí)例抱冷,那么str就是StringBuffer的引用。
內(nèi)存分配
此時(shí)梢褐,運(yùn)行一個(gè)賦值語(yǔ)句:
對(duì)象持有多個(gè)強(qiáng)引用
那么旺遮,str所指向的對(duì)象也將被str1所指向,同時(shí)在局部椨龋空間上會(huì)分配空間存放str1變量耿眉。此時(shí),該StringBuffer實(shí)例就有兩個(gè)引用猪贪。對(duì)引用的”==”操作用于表示兩個(gè)操作數(shù)所指向的堆空間地址是否相同跷敬,不表示兩個(gè)操作數(shù)所指向的對(duì)象是否相等。
多個(gè)強(qiáng)引用
強(qiáng)引用特點(diǎn):
強(qiáng)引用可以直接訪(fǎng)問(wèn)目標(biāo)對(duì)象热押。
強(qiáng)引用所指向的對(duì)象在任何時(shí)候都不會(huì)被系統(tǒng)回收西傀。JVM寧愿拋出OOM異常,也不會(huì)回收強(qiáng)引用所指向的對(duì)象桶癣。
強(qiáng)引用可能導(dǎo)致內(nèi)存泄露拥褂。
軟引用:如果一個(gè)對(duì)象只具有軟引用,那么如果內(nèi)存空間足夠牙寞,GC就不會(huì)回收它饺鹃;如果內(nèi)存空間不足了,就會(huì)回收這些對(duì)象的內(nèi)存间雀。 軟引用可用來(lái)實(shí)現(xiàn)內(nèi)存敏感的高速緩存悔详。
軟引用是除了強(qiáng)引用外,最強(qiáng)的引用類(lèi)型惹挟∏洋Γ可以通過(guò)java.lang.ref.SoftReference使用軟引用。一個(gè)持有軟引用的對(duì)象连锯,不會(huì)被JVM很快回收归苍,JVM會(huì)根據(jù)當(dāng)前堆的使用情況來(lái)判斷何時(shí)回收。當(dāng)堆的使用率臨近閾值時(shí)运怖,才會(huì)回收軟引用的對(duì)象拼弃。 看下我工作中使用到軟引用的場(chǎng)景,加載一個(gè)1080x1920分辨率的圖摇展,約900多K, 對(duì)于我們來(lái)說(shuō)吻氧,這個(gè)圖已是非常大了。
軟引用示例
首先通過(guò)BitmapFactory.decodeStream構(gòu)造一個(gè)大圖bitmap
然后把這個(gè)bitmap轉(zhuǎn)成Drawble類(lèi)型,構(gòu)成強(qiáng)引用医男。
接著使用SoftReference構(gòu)造這個(gè)drawable對(duì)象的軟引用drawables.
最后通過(guò)軟引用的get()方法砸狞,取得drawable對(duì)象實(shí)例的強(qiáng)引用捻勉,發(fā)現(xiàn)對(duì)象被未回收镀梭。在GC在內(nèi)存充足的情況下,不會(huì)回收軟引用對(duì)象踱启。
在實(shí)際中报账,一起請(qǐng)求很多相關(guān)圖片,從網(wǎng)絡(luò)埠偿,這時(shí)就會(huì)請(qǐng)求非常多的內(nèi)存空間透罢,導(dǎo)致內(nèi)存吃緊,系統(tǒng)開(kāi)始會(huì)GC冠蒋。這次GC后羽圃,drawables.get()不再返回Drawable對(duì)象,而是返回null抖剿,這時(shí)屏幕上背景圖不顯示朽寞,說(shuō)明在系統(tǒng)內(nèi)存緊張的情況下,軟引用被回收斩郎。
使用軟引用以后脑融,在OutOfMemory異常發(fā)生之前,這些緩存的圖片資源的內(nèi)存空間可以被釋放掉的缩宜,從而避免內(nèi)存達(dá)到上限肘迎,避免Crash發(fā)生。
需要注意的是锻煌,在垃圾回收器對(duì)這個(gè)Java對(duì)象回收前妓布,SoftReference類(lèi)所提供的get方法會(huì)返回Java對(duì)象的強(qiáng)引用,一旦垃圾線(xiàn)程回收該Java對(duì)象之后宋梧,get方法將返回null匣沼。所以在獲取軟引用對(duì)象的代碼中,一定要判斷是否為null乃秀,以免出現(xiàn)NullPointerException異常導(dǎo)致應(yīng)用崩潰肛著。
到底什么時(shí)候使用軟引用,什么時(shí)候使用弱引用呢跺讯?
個(gè)人認(rèn)為枢贿,如果只是想避免OutOfMemory異常的發(fā)生,則可以使用軟引用刀脏。如果對(duì)于應(yīng)用的性能更在意局荚,想盡快回收一些占用內(nèi)存比較大的對(duì)象,則可以使用弱引用。
還有就是可以根據(jù)對(duì)象是否經(jīng)常使用來(lái)判斷耀态。如果該對(duì)象可能會(huì)經(jīng)常使用的轮傍,就盡量用軟引用。如果該對(duì)象不被使用的可能性更大些首装,就可以用弱引用创夜。
另外,和弱引用功能類(lèi)似的是WeakHashMap仙逻。WeakHashMap對(duì)于一個(gè)給定的key驰吓,其映射的存在并不阻止垃圾回收器對(duì)該鍵的回收,回收以后系奉,其條目從映射中有效地移除檬贰。WeakHashMap使用ReferenceQueue實(shí)現(xiàn)的這種機(jī)制。
弱引用:如果一個(gè)對(duì)象只具有弱引用缺亮,那么在垃圾回收器線(xiàn)程掃描的過(guò)程中翁涤,一旦發(fā)現(xiàn)了只具有弱引用的對(duì)象,不管當(dāng)前內(nèi)存空間足夠與否萌踱,都會(huì)回收它的內(nèi)存葵礼。不過(guò),由于垃圾回收器是一個(gè)優(yōu)先級(jí)很低的線(xiàn)程虫蝶,因此不一定會(huì)很快發(fā)現(xiàn)那些只具有弱引用的對(duì)象章咧。
弱引用是一種比軟引用較弱的引用類(lèi)型。在系統(tǒng)GC時(shí)能真,只要發(fā)現(xiàn)弱引用赁严,不管系統(tǒng)堆空間是否足夠,都會(huì)將對(duì)象進(jìn)行回收粉铐。但是疼约,由于垃圾回收器的線(xiàn)程通常優(yōu)先級(jí)很低,因此蝙泼,并一不定能很快的發(fā)現(xiàn)持有弱引用的對(duì)象程剥。這種情況下,弱引用對(duì)象可以存在較長(zhǎng)的一段時(shí)間汤踏。一旦一個(gè)弱引用對(duì)象被垃圾回收器回收织鲸,便會(huì)加入到一個(gè)注冊(cè)引用隊(duì)列中。 看一個(gè)工作中實(shí)例:播放器的播放Panel溪胶,是一個(gè)View搂擦,就是在視頻播放時(shí),可以show哗脖、hide, 也可以拖拽進(jìn)度條之類(lèi)瀑踢,還有上面的音量扳还,亮度調(diào)節(jié)等。這樣一個(gè)view橱夭,我們用弱引用氨距,因?yàn)樵谝曨l播放過(guò)程中,不論硬解還是軟解棘劣,都將占用大量?jī)?nèi)存俏让。保證視頻的渲染效果。 在VideoControllerView.java 有如下一段代碼:
弱應(yīng)用示例
在GC之前呈础,弱引用對(duì)象并未被垃圾回收器發(fā)現(xiàn)舆驶,因此通過(guò)mView.get()方法可以取得對(duì)應(yīng)的強(qiáng)引用橱健。但是只要進(jìn)行垃圾回收而钞,弱引用對(duì)象一旦被發(fā)現(xiàn),便會(huì)立即被回收拘荡,并加入注冊(cè)引用隊(duì)列中臼节。此時(shí),再次通過(guò)mView.get()方法取得強(qiáng)引用就會(huì)失敗珊皿。
注意:軟引用网缝,弱引用都非常適合來(lái)保存那些可有可無(wú)的緩存數(shù)據(jù)。如果這樣做蟋定,當(dāng)系統(tǒng)內(nèi)存不足時(shí)粉臊,這些緩存數(shù)據(jù)會(huì)被回收,不會(huì)導(dǎo)致內(nèi)存溢出驶兜。而當(dāng)內(nèi)存資源充足時(shí)扼仲,這些緩存數(shù)據(jù)又可以存在相當(dāng)長(zhǎng)的時(shí)間。
虛引用:一個(gè)持有虛引用的對(duì)象抄淑,和沒(méi)有引用幾乎是一樣的屠凶,隨時(shí)都可能被垃圾回收器回收。
虛引用是所有引用類(lèi)型中最弱的一個(gè)肆资。一個(gè)持有虛引用的對(duì)象矗愧,和沒(méi)有引用幾乎是一樣的,隨時(shí)都可能被垃圾回收器回收郑原。當(dāng)試圖通過(guò)虛引用的get()方法取得強(qiáng)引用時(shí)唉韭,總是會(huì)失敗。并且犯犁,虛引用必須和引用隊(duì)列一起使用属愤,它的作用在于跟蹤垃圾回收過(guò)程。
當(dāng)垃圾回收器準(zhǔn)備回收一個(gè)對(duì)象時(shí)栖秕,如果發(fā)現(xiàn)它還有虛引用春塌,就會(huì)在垃圾回收后,銷(xiāo)毀這個(gè)對(duì)象,獎(jiǎng)這個(gè)虛引用加入引用隊(duì)列只壳。
實(shí)際中幾乎沒(méi)用俏拱,暫不介紹。
在java.lang.ref包中提供了幾個(gè)類(lèi):SoftReference類(lèi)吼句、WeakReference類(lèi)和PhantomReference類(lèi)锅必,它們分別代表軟引用、弱引用和虛引用惕艳。
ReferenceQueue類(lèi)表示引用隊(duì)列搞隐,它可以和這三種引用類(lèi)聯(lián)合使用,以便跟蹤Java虛擬機(jī)回收所引用的對(duì)象的活動(dòng)远搪。