前言
本文章會一步一步的探討內(nèi)存泄露的問題。
博主第一次書寫長篇技術(shù)貼,如有錯誤或不周到的地方請多指教潜慎。
JAVA是垃圾回收語言的一種爪幻,開發(fā)者無需特意管理內(nèi)存分配。但是JAVA中還是存在著許多內(nèi)存泄露的可能性肠套,如果不好好處理內(nèi)存泄露,會導(dǎo)致APP內(nèi)存單元無法釋放被浪費掉,最終導(dǎo)致內(nèi)存全部占據(jù)堆棧(heap)擠爆進(jìn)而程序崩潰憔辫。
內(nèi)存泄露
說到內(nèi)存泄露,就不得不提到內(nèi)存溢出仿荆,這兩個比較容易混淆的概念贰您,我們來分析一下。
內(nèi)存泄露:程序在向系統(tǒng)申請分配內(nèi)存空間后(new)拢操,在使用完畢后未釋放锦亦。結(jié)果導(dǎo)致一直占據(jù)該內(nèi)存單元,我們和程序都無法再使用該內(nèi)存單元令境,直到程序結(jié)束杠园,這是內(nèi)存泄露。
內(nèi)存溢出:程序向系統(tǒng)申請的內(nèi)存空間超出了系統(tǒng)能給的舔庶。比如內(nèi)存只能分配一個int類型抛蚁,我卻要塞給他一個long類型玲昧,系統(tǒng)就出現(xiàn)oom。又比如一車最多能坐5個人篮绿,你卻非要塞下10個孵延,車就擠爆了。
大量的內(nèi)存泄露會導(dǎo)致內(nèi)存溢出(oom)亲配。
內(nèi)存
想要了解內(nèi)存泄露尘应,對內(nèi)存的了解必不可少。
JAVA是在JVM所虛擬出的內(nèi)存環(huán)境中運行的吼虎,JVM的內(nèi)存可分為三個區(qū):堆(heap)犬钢、棧(stack)和方法區(qū)(method)。
棧(stack):是簡單的數(shù)據(jù)結(jié)構(gòu)思灰,但在計算機(jī)中使用廣泛玷犹。棧最顯著的特征是:LIFO(Last In, First Out, 后進(jìn)先出)。比如我們往箱子里面放衣服洒疚,先放入的在最下方歹颓,只有拿出后來放入的才能拿到下方的衣服。棧中只存放基本類型和對象的引用(不是對象)油湖。
堆(heap):堆內(nèi)存用于存放由new創(chuàng)建的對象和數(shù)組巍扛。在堆中分配的內(nèi)存,由java虛擬機(jī)自動垃圾回收器來管理乏德。JVM只有一個堆區(qū)(heap)被所有線程共享撤奸,堆中不存放基本類型和對象引用,只存放對象本身喊括。
方法區(qū)(method):又叫靜態(tài)區(qū)胧瓜,跟堆一樣,被所有的線程共享郑什。方法區(qū)包含所有的class和static變量府喳。
內(nèi)存的概念大概理解清楚后,要考慮的問題來了:
到底是哪里的內(nèi)存會讓我們造成內(nèi)存泄露蹦误?
好了黑人兄弟既然提問了劫拢,那我們繼續(xù)來嘮!
內(nèi)存泄露原因分析
在JAVA中JVM的棧記錄了方法的調(diào)用强胰,每個線程擁有一個棧。在線程的運行過程當(dāng)中妹沙,執(zhí)行到一個新的方法調(diào)用偶洋,就在棧中增加一個內(nèi)存單元,即幀(frame)距糖。在frame中玄窝,保存有該方法調(diào)用的參數(shù)牵寺、局部變量和返回地址。然而JAVA中的局部變量只能是基本類型變量(int)恩脂,或者對象的引用帽氓。所以在棧中只存放基本類型變量和對象的引用。引用的對象保存在堆中俩块。
當(dāng)某方法運行結(jié)束時黎休,該方法對應(yīng)的frame將會從棧中刪除,frame中所有局部變量和參數(shù)所占有的空間也隨之釋放玉凯。線程回到原方法繼續(xù)執(zhí)行势腮,當(dāng)所有的棧都清空的時候,程序也就隨之運行結(jié)束漫仆。
而對于堆內(nèi)存捎拯,堆存放著普通變量。在JAVA中堆內(nèi)存不會隨著方法的結(jié)束而清空盲厌,所以在方法中定義了局部變量署照,在方法結(jié)束后變量依然存活在堆中。
綜上所述吗浩,棧(stack)可以自行清除不用的內(nèi)存空間藤树。但是如果我們不停的創(chuàng)建新對象,堆(heap)的內(nèi)存空間就會被消耗盡拓萌。所以JAVA引入了垃圾回收(garbage collection岁钓,簡稱GC)去處理堆內(nèi)存的回收,但如果對象一直被引用無法被回收微王,造成內(nèi)存的浪費屡限,無法再被使用。所以對象無法被GC回收就是造成內(nèi)存泄露的原因炕倘!
垃圾回收機(jī)制
垃圾回收(garbage collection钧大,簡稱GC)可以自動清空堆中不再使用的對象。在JAVA中對象是通過引用使用的罩旋。如果再沒有引用指向該對象啊央,那么該對象就無從處理或調(diào)用該對象,這樣的對象稱為不可到達(dá)(unreachable)涨醋。垃圾回收用于釋放不可到達(dá)的對象所占據(jù)的內(nèi)存瓜饥。
實現(xiàn)思想:我們將棧定義為root,遍歷棧中所有的對象的引用浴骂,再遍歷一遍堆中的對象乓土。因為棧中的對象的引用執(zhí)行完畢就刪除,所以我們就可以通過棧中的對象的引用,查找到堆中沒有被指向的對象趣苏,這些對象即為不可到達(dá)對象狡相,對其進(jìn)行垃圾回收。
如果持有對象的強(qiáng)引用食磕,垃圾回收器是無法在內(nèi)存中回收這個對象尽棕。
引用類型
在JDK 1.2以前的版本中,若一個對象不被任何變量引用彬伦,那么程序就無法再使用這個對象滔悉。也就是說,只有對象處于可觸及(reachable)狀態(tài)媚朦,程序才能使用它氧敢。從JDK 1.2版本開始,把對象的引用分為4種級別询张,從而使程序能更加靈活地控制對象的生命周期孙乖。這4種級別由高到低依次為:強(qiáng)引用、軟引用份氧、弱引用和虛引用唯袄。
Java/Android引用類型及其使用分析
1. 強(qiáng)引用(Strong reference)
實際編碼中最常見的一種引用類型。常見形式如:A a = new A();等蜗帜。強(qiáng)引用本身存儲在棧內(nèi)存中恋拷,其存儲指向?qū)?nèi)存中對象的地址。一般情況下厅缺,當(dāng)對內(nèi)存中的對象不再有任何強(qiáng)引用指向它時蔬顾,垃圾回收機(jī)器開始考慮可能要對此內(nèi)存進(jìn)行的垃圾回收。如當(dāng)進(jìn)行編碼:a = null湘捎,此時诀豁,剛剛在堆中分配地址并新建的a對象沒有其他的任何引用,當(dāng)系統(tǒng)進(jìn)行垃圾回收時窥妇,堆內(nèi)存將被垃圾回收舷胜。
2. 軟引用(Soft Reference)
軟引用的一般使用形式如下:
A a = new A();
SoftReference<A> srA = new SoftReference<A>(a);
軟引用所指示的對象進(jìn)行垃圾回收需要滿足如下兩個條件:
1.當(dāng)其指示的對象沒有任何強(qiáng)引用對象指向它;
2.當(dāng)虛擬機(jī)內(nèi)存不足時活翩。
因此烹骨,SoftReference變相的延長了其指示對象占據(jù)堆內(nèi)存的時間,直到虛擬機(jī)內(nèi)存不足時垃圾回收器才回收此堆內(nèi)存空間材泄。
3. 弱引用(Weak Reference)
同樣的沮焕,軟引用的一般使用形式如下:
A a = new A();
WeakReference<A> wrA = new WeakReference<A>(a);
WeakReference不改變原有強(qiáng)引用對象的垃圾回收時機(jī),一旦其指示對象沒有任何強(qiáng)引用對象時脸爱,此對象即進(jìn)入正常的垃圾回收流程遇汞。
4. 虛引用(Phantom Reference)
與SoftReference或WeakReference相比,PhantomReference主要差別體現(xiàn)在如下幾點:
1.PhantomReference只有一個構(gòu)造函數(shù)
PhantomReference(T referent, ReferenceQueue<? super T> q)
2.不管有無強(qiáng)引用指向PhantomReference的指示對象簿废,PhantomReference的get()方法返回結(jié)果都是null空入。
因此,PhantomReference使用必須結(jié)合ReferenceQueue族檬;
與WeakReference相同歪赢,PhantomReference并不會改變其指示對象的垃圾回收時機(jī)。
內(nèi)存泄露原因
如果持有對象的強(qiáng)引用单料,垃圾回收器是無法在內(nèi)存中回收這個對象埋凯。
內(nèi)存泄露的真因是:持有對象的強(qiáng)引用,且沒有及時釋放扫尖,進(jìn)而造成內(nèi)存單元一直被占用白对,浪費空間,甚至可能造成內(nèi)存溢出换怖!
其實在Android中會造成內(nèi)存泄露的情景無外乎兩種:
- 全局進(jìn)程(process-global)的static變量甩恼。這個無視應(yīng)用的狀態(tài),持有Activity的強(qiáng)引用的怪物沉颂。
- 活在Activity生命周期之外的線程条摸。沒有清空對Activity的強(qiáng)引用。
檢查一下你的項目中是否有以下幾種情況:
- Static Activities
- Static Views
- Inner Classes
- Anonymous Classes
- Handler
- Threads
- TimerTask
- Sensor Manager
詳解見該文章《Android內(nèi)存泄漏的八種可能》
最后推薦一個可檢測app內(nèi)存泄露的項目:LeakCanary(可以檢測app的內(nèi)存泄露)
總結(jié)
雖然現(xiàn)在手機(jī)內(nèi)存在不停的提升铸屉,內(nèi)存泄露興許不會像dalvik時代由于虛擬機(jī)內(nèi)存過小造成各種花樣oom钉蒲。但是過量的內(nèi)存泄露依然會造成內(nèi)存溢出,影響用戶體驗彻坛,在如今定制系統(tǒng)層出不窮顷啼、機(jī)型花樣越來越多的情況下解決好內(nèi)存泄露的問題會讓適配和穩(wěn)定性進(jìn)一步提高!
希望我的文章能給大家?guī)硪稽c點的福利昌屉,那在下就足夠開心了钙蒙。
下次再見!