前言
對于C++來說,內(nèi)存泄漏就是new出來的對象沒有delete,俗稱野指針;對于Java來說,就是new出來的Object 放在Heap上無法被GC回收;本文通過QQ和Qzone中內(nèi)存泄漏實(shí)例來講android中內(nèi)存泄漏分析解法和編寫代碼應(yīng)注意的事項(xiàng)。
Java 中的內(nèi)存分配
靜態(tài)儲存區(qū):編譯時(shí)就分配好,在程序整個運(yùn)行期間都存在。它主要存放靜態(tài)數(shù)據(jù)和常量;
棧區(qū):當(dāng)方法執(zhí)行時(shí),會在棧區(qū)內(nèi)存中創(chuàng)建方法體內(nèi)部的局部變量,方法結(jié)束后自動釋放內(nèi)存;
堆區(qū):通常存放 new 出來的對象。由 Java 垃圾回收器回收。
四種引用類型的介紹
強(qiáng)引用(StrongReference):JVM 寧可拋出 OOM ,也不會讓 GC 回收具有強(qiáng)引用的對象;
軟引用(SoftReference):只有在內(nèi)存空間不足時(shí),才會被回的對象;
弱引用(WeakReference):在 GC 時(shí),一旦發(fā)現(xiàn)了只具有弱引用的對象,不管當(dāng)前內(nèi)存空間足夠與否,都會回收它的內(nèi)存;
虛引用(PhantomReference):任何時(shí)候都可以被GC回收,當(dāng)垃圾回收器準(zhǔn)備回收一個對象時(shí),如果發(fā)現(xiàn)它還有虛引用,就會在回收對象的內(nèi)存之前,把這個虛引用加入到與之關(guān)聯(lián)的引用隊(duì)列中。程序可以通過判斷引用隊(duì)列中是否存在該對象的虛引用,來了解這個對象是否將要被回收??梢杂脕碜鳛镚C回收Object的標(biāo)志。
我們常說的內(nèi)存泄漏是指new出來的Object無法被GC回收,即為強(qiáng)引用:
內(nèi)存泄漏發(fā)生時(shí)的主要表現(xiàn)為內(nèi)存抖動,可用內(nèi)存慢慢變少:
Andriod中分析內(nèi)存泄漏的工具M(jìn)AT
MAT(Memory Analyzer Tools)是一個 Eclipse 插件,它是一個快速、功能豐富的JAVA heap分析工具,它可以幫助我們查找內(nèi)存泄漏和減少內(nèi)存消耗。
MAT 插件的下載地址:www.eclipse.org/mat
MAT 使用方法介紹:
http://www.cnblogs.com/larack/p/6071209.html
QQ和Qzone內(nèi)存泄漏如何監(jiān)控
QQ和Qzone 的內(nèi)存泄漏采用SNGAPM解決方案,SNGAPM是一個性能監(jiān)控、分析的統(tǒng)一解決方案,它從終端收集性能信息,上報(bào)到一個后臺,后臺將監(jiān)控類信息聚合展示為圖表,將分析類信息進(jìn)行分析并提單,通知開發(fā)者;
SNGAPM由App(MagnifierApp)和
web server(MagnifierServer)兩部分組成;
MagnifierApp在自動內(nèi)存泄漏檢測中是一個銜接檢測組件(LeakInspector)和自動化云分析(MagnifierCloud)的中間性平臺,它從LeakInspector的內(nèi)存dump自動化上傳MagnifierServer;
MagnifierServer后臺會定時(shí)提交分析任務(wù)到MagnifierCloud;
MagnifierCloud分析結(jié)束之后會更新數(shù)據(jù)到magnifier web上,同時(shí)以bug單形式通知開發(fā)者。
常見的內(nèi)存泄漏案例
case 1. 單例造成的內(nèi)存泄露
單例的靜態(tài)特性導(dǎo)致其生命周期同應(yīng)用一樣長。
解決方案:
將該屬性的引用方式改為弱引用;
如果傳入Context,使用ApplicationContext;
example:
泄漏代碼片段


case 2. InnerClass匿名內(nèi)部類
在Java中,非靜態(tài)內(nèi)部類 和 匿名類 都會潛在的引用它們所屬的外部類,但是,靜態(tài)內(nèi)部類卻不會。如果這個非靜態(tài)內(nèi)部類實(shí)例做了一些耗時(shí)的操作,就會造成外圍對象不會被回收,從而導(dǎo)致內(nèi)存泄漏。
解決方案:
將內(nèi)部類變成靜態(tài)內(nèi)部類;
如果有強(qiáng)引用Activity中的屬性,則將該屬性的引用方式改為弱引用;
在業(yè)務(wù)允許的情況下,當(dāng)Activity執(zhí)行onDestory時(shí),結(jié)束這些耗時(shí)任務(wù);
example:


case 3. Activity Context 的不正確使用
在Android應(yīng)用程序中通??梢允褂脙煞NContext對象:Activity和Application。當(dāng)類或方法需要Context對象的時(shí)候常見的做法是使用第一個作為Context參數(shù)。這樣就意味著View對象對整個Activity保持引用,因此也就保持對Activty的所有的引用。
假設(shè)一個場景,當(dāng)應(yīng)用程序有個比較大的Bitmap類型的圖片,每次旋轉(zhuǎn)是都重新加載圖片所用的時(shí)間較多。為了提高屏幕旋轉(zhuǎn)是Activity的創(chuàng)建速度,最簡單的方法時(shí)將這個Bitmap對象使用Static修飾。 當(dāng)一個Drawable綁定在View上,實(shí)際上這個View對象就會成為這份Drawable的一個Callback成員變量。而靜態(tài)變量的生命周期要長于Activity。導(dǎo)致了當(dāng)旋轉(zhuǎn)屏幕時(shí),Activity無法被回收,而造成內(nèi)存泄露。
解決方案:
使用ApplicationContext代替ActivityContext,因?yàn)锳pplicationContext會隨著應(yīng)用程序的存在而存在,而不依賴于activity的生命周期;
對Context的引用不要超過它本身的生命周期,慎重的對Context使用“static”關(guān)鍵字。Context里如果有線程,一定要在onDestroy()里及時(shí)停掉。
example:

case 4. Handler引起的內(nèi)存泄漏
當(dāng)Handler中有延遲的的任務(wù)或是等待執(zhí)行的任務(wù)隊(duì)列過長,由于消息持有對Handler的引用,而Handler又持有對其外部類的潛在引用,這條引用關(guān)系會一直保持到消息得到處理,而導(dǎo)致了Activity無法被垃圾回收器回收,而導(dǎo)致了內(nèi)存泄露。
解決方案:
可以把Handler類放在單獨(dú)的類文件中,或者使用靜態(tài)內(nèi)部類便可以避免泄露;
如果想在Handler內(nèi)部去調(diào)用所在的Activity,那么可以在handler內(nèi)部使用弱引用的方式去指向所在Activity.使用Static + WeakReference的方式來達(dá)到斷開Handler與Activity之間存在引用關(guān)系的目的。
Solution

case 5. 注冊監(jiān)聽器的泄漏
系統(tǒng)服務(wù)可以通過Context.getSystemService 獲取,它們負(fù)責(zé)執(zhí)行某些后臺任務(wù),或者為硬件訪問提供接口。如果Context 對象想要在服務(wù)內(nèi)部的事件發(fā)生時(shí)被通知,那就需要把自己注冊到服務(wù)的監(jiān)聽器中。然而,這會讓服務(wù)持有Activity 的引用,如果在Activity onDestory時(shí)沒有釋放掉引用就會內(nèi)存泄漏。
解決方案:
使用ApplicationContext代替ActivityContext;
在Activity執(zhí)行onDestory時(shí),調(diào)用反注冊;
mSensorManager = (SensorManager)this.getSystemService(Context.SENSOR_SERVICE);
Solution:
mSensorManager = (SensorManager) getApplicationContext().getSystemService(Context.SENSOR_SERVICE);
下面是容易造成內(nèi)存泄漏的系統(tǒng)服務(wù):
InputMethodManager imm = (InputMethodManager) context.getApplicationContext().getSystemService(Context.INPUT_METHOD_SERVICE);

case 6. Cursor,Stream沒有close,View沒有recyle
資源性對象比如(Cursor,F(xiàn)ile文件等)往往都用了一些緩沖,我們在不使用的時(shí)候,應(yīng)該及時(shí)關(guān)閉它們,以便它們的緩沖及時(shí)回收內(nèi)存。它們的緩沖不僅存在于 java虛擬機(jī)內(nèi),還存在于java虛擬機(jī)外。如果我們僅僅是把它的引用設(shè)置為null,而不關(guān)閉它們,往往會造成內(nèi)存泄漏。因?yàn)橛行┵Y源性對象,比如SQLiteCursor(在析構(gòu)函數(shù)finalize(),如果我們沒有關(guān)閉它,它自己會調(diào)close()關(guān)閉),如果我們沒有關(guān)閉它,系統(tǒng)在回收它時(shí)也會關(guān)閉它,但是這樣的效率太低了。因此對于資源性對象在不使用的時(shí)候,應(yīng)該調(diào)用它的close()函數(shù),將其關(guān)閉掉,然后才置為null. 在我們的程序退出時(shí)一定要確保我們的資源性對象已經(jīng)關(guān)閉。
Solution:
調(diào)用onRecycled()

case 7. 集合中對象沒清理造成的內(nèi)存泄漏
我們通常把一些對象的引用加入到了集合容器(比如ArrayList)中,當(dāng)我們不需要該對象時(shí),并沒有把它的引用從集合中清理掉,這樣這個集合就會越來越大。如果這個集合是static的話,那情況就更嚴(yán)重了。
所以要在退出程序之前,將集合里的東西clear,然后置為null,再退出程序。
解決方案:
在Activity退出之前,將集合里的東西clear,然后置為null,再退出程序。
Solution

case 8. WebView造成的泄露
當(dāng)我們不要使用WebView對象時(shí),應(yīng)該調(diào)用它的destory()函數(shù)來銷毀它,并釋放其占用的內(nèi)存,否則其占用的內(nèi)存長期也不能被回收,從而造成內(nèi)存泄露。
解決方案:
為webView開啟另外一個進(jìn)程,通過AIDL與主線程進(jìn)行通信,WebView所在的進(jìn)程可以根據(jù)業(yè)務(wù)的需要選擇合適的時(shí)機(jī)進(jìn)行銷毀,從而達(dá)到內(nèi)存的完整釋放。
case 9. 構(gòu)造Adapter時(shí),沒有使用緩存的ConvertView
初始時(shí)ListView會從Adapter中根據(jù)當(dāng)前的屏幕布局實(shí)例化一定數(shù)量的View對象,同時(shí)ListView會將這些View對象 緩存起來。
當(dāng)向上滾動ListView時(shí),原先位于最上面的List Item的View對象會被回收,然后被用來構(gòu)造新出現(xiàn)的最下面的List Item。
這個構(gòu)造過程就是由getView()方法完成的,getView()的第二個形參View ConvertView就是被緩存起來的List Item的View對象(初始化時(shí)緩存中沒有View對象則ConvertView是null)。
本文是我轉(zhuǎn)自轉(zhuǎn)自: QQ空間終端開發(fā)團(tuán)隊(duì),歡迎關(guān)注?。ㄎ恼聝H僅為了讓更多的android愛好者學(xué)習(xí)使用)