Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> 如何定位和解決Android的內存溢出問題(大總結)

如何定位和解決Android的內存溢出問題(大總結)

編輯:關於Android編程

我們經常在做項目過程中遇到內存溢出的問題,同時面試中關於OOM的問題也常常出現。

這裡,我將前輩們解決Andorid內存溢出的方法重新整理一番,方便自己以後使用。

一、Android的內存機制

android應用層是由java開發的,android的davlik虛擬機與jvm也類似,只不過它是基於寄存器的。在java中,通過new為對象分配內存,所有對象在java堆內分配空間;而內存的釋放是由垃圾收集器(GC)來回收的。 Java采用了有向圖的原理。Java將引用關系考慮為圖的有向邊,有向邊從引用者指向引用對象。線程對象可以作為有向圖的起始頂點,該圖就是從起始頂點(GC roots)開始的一棵樹,根頂點可以到達的對象都是有效對象,GC不會回收這些對象。如果某個對象 (連通子圖)與這個根頂點不可達(注意,該圖為有向圖),那麼我們認為這個(這些)對象不再被引用,可以被GC回收。

二、Android的內存溢出原因

1、內存洩露導致

由於我們程序的失誤,長期保持某些資源(如Context)的引用,垃圾回收器就無法回收它,當然該對象占用的內存就無法被使用,這就造成內存洩露。

Android中常見就是Activity被引用在調用finish之後卻沒有釋放,第二次打開activity又重新創建,這樣的內存洩露不斷的發生,則會導致內存的溢出。

Android的每個應用程序都會使用一個專有的Dalvik虛擬機實例來運行,它是由Zygote服務進程孵化出來的,也就是說每個應用程序都是在屬於自己的進程中運行的。Android為不同類型的進程分配了不同的內存使用上限,如果程序在運行過程中出現了內存洩漏的而造成應用進程使用的內存超過了這個上限,則會被系統視為內存洩漏,從而被kill掉,這使得僅僅自己的進程被kill掉,而不會影響其他進程.

2、占用內存較多的對象

保存了多個耗用內存過大的對象(如Bitmap)或加載單個超大的圖片,造成內存超出限制。

三、常見的內存洩漏問題及其解決方案

1、引用沒釋放造成的內存洩露

1.1注冊沒取消造成的內存洩露

這種Android的內存洩露比純Java的內存洩漏還要嚴重,因為其他一些Android程序可能引用系統的Android程序的對象(比如注冊機制)。即使Android程序已經結束了,但是別的應用程序仍然還有對Android程序的某個對象的引用,洩漏的內存依然不能被垃圾回收。

1.2集合中對象沒清理造成的內存洩露

我們通常把一些對象的引用加入到了集合中,當我們不需要該對象時,並沒有把它的引用從集合中清理掉,這樣這個集合就會越來越大。如果這個集合是static的話,那情況就更嚴重了。

1.3static

static是Java中的一個關鍵字,當用它來修飾成員變量時,那麼該變量就屬於該類,而不是該類的實例。

private static ActivitymContext; //省略

如何才能有效的避免這種引用的發生呢?

第一,應該盡量避免static成員變量引用資源耗費過多的實例,比如Context。

第二、Context盡量使用ApplicationContext,因為Application的Context的生命周期比較長,引用它不會出現內存洩露的問題。

看使用的周期是否在activity周期內,如果超出,必須用application;常見的情景包括:AsyncTask,Thread,第三方庫初始化等等。

還有些情景,只能用activity:比如,對話框,各種View,需要startActivity的等。

總之,盡可能使用Application。

第三、使用WeakReference代替強引用。比如可以使用WeakReferencemContextRef;

1.4、線程(內部類的使用)

線程產生內存洩露的主要原因在於線程生命周期的不可控。如果我們的線程是Activity的內部類,所以MyThread中保存了Activity的一個引用,當MyThread的run函數沒有結束時,MyThread是不會被銷毀的,因此它所引用的老的Activity也不會被銷毀,因此就出現了內存洩露的問題。

如果非靜態內部類的方法中,有生命周期大於其所在類的,那就有問題了。比如:AsyncTask、Handler,這兩個類都是方便開發者執行異步任務的,但是,這兩個都跳出了Activity/Fragment的生命周期。

解決方案

第一、將線程的內部類,改為靜態內部類。

原因:

因為非靜態內部類會自動持有一個所屬類的實例,如果所屬類的實例已經結束生命周期,但內部類的方法仍在執行,就會hold其主體(引用)。也就使主體不能被釋放,亦即內存洩露。靜態類編譯後和非內部類是一樣的,有自己獨立的類名。不會悄悄引用所屬類的實例,所以就不容易洩露。

第二、如果需要引用Acitivity,使用弱引用。

2、資源對象沒關閉造成的內存洩露

資源性對象比如(Cursor,File文件等)往往都用了一些緩沖,我們在不使用的時候,應該及時關閉它們,以便它們的緩沖及時回收內存。而不是等待GC來處理。它們的緩沖不僅存在於java虛擬機內,還存在於java虛擬機外。如果我們僅僅是把它的引用設置為null,而不關閉它們,往往會造成內存洩露。因為有些資源性對象,比如SQLiteCursor(在析構函數finalize(),如果我們沒有關閉它,它自己會調close()關閉),如果我們沒有關閉它,系統在回收它時也會關閉它,但是這樣的效率太低了。而且android數據庫中對Cursor資源的是又限制個數的,如果不及時close掉,會導致別的地方無法獲得。

3、一些不良代碼成內存壓力

有些代碼並不造成內存洩露,但是它們,或是對沒使用的內存沒進行有效及時的釋放,或是沒有有效的利用已有的對象而是頻繁的申請新內存,對內存的回收和分配造成很大影響的,容易迫使虛擬機不得不給該應用進程分配更多的內存,造成不必要的內存開支。

3.1 Bitmap沒調用recycle()

Bitmap對象在不使用時,我們應該先調用recycle()釋放內存,然後才設置為null.

雖然recycle()從源碼上看,調用它應該能立即釋放Bitmap的主要內存,但是測試結果顯示它並沒能立即釋放內存。但是我猜應該還是能大大的加速Bitmap的主要內存的釋放。

3.2 構造Adapter時,沒有使用緩存的convertView

以構造ListView的BaseAdapter為例,在BaseAdapter中提共了方法:

publicViewgetView(intposition,ViewconvertView,ViewGroupparent)

來向ListView提供每一個item所需要的view對象。初始時ListView會從BaseAdapter中根據當前的屏幕布局實例化一定數量的view對象,同時ListView會將這些view對象緩存起來。當向上滾動ListView時,原先位於最上面的listitem的view對象會被回收,然後被用來構造新出現的最下面的listitem。這個構造過程就是由getView()方法完成的,getView()的第二個形參ViewconvertView就是被緩存起來的listitem的view對象(初始化時緩存中沒有view對象則convertView是null)。

由此可以看出,如果我們不去使用convertView,而是每次都在getView()中重新實例化一個View對象的話,即浪費時間,也造成內存垃圾,給垃圾回收增加壓力,如果垃圾回收來不及的話,虛擬機將不得不給該應用進程分配更多的內存,造成不必要的內存開支。

四、占用內存較多的對象(圖片過大)造成內存溢出及其解決方案

因為Bitmap占用的內存實在是太多了,特別是分辨率大的圖片,如果要顯示多張那問題就更顯著了。Android分配給Bitmap的大小只有8M。

方法1:等比例縮小圖片

有時候,我們要顯示的區域很小,沒有必要將整個圖片都加載出來,而只需要記載一個縮小過的圖片,這時候可以設置一定的采樣率,那麼就可以大大減小占用的內存。

1BitmapFactory.Options options =newBitmapFactory.Options();

2options.inSampleSize =2;//圖片寬高都為原來的二分之一,即圖片為原來的四分之一

盡量不要使用setImageBitmap或setImageResource或BitmapFactory.decodeResource來設置一張大圖,

因為這些函數在完成decode後,最終都是通過java層的createBitmap來完成的,需要消耗更多內存。

因此,改用先通過BitmapFactory.decodeStream方法,創建出一個bitmap,再將其設為ImageView的 source,

decodeStream最大的秘密在於其直接調用JNI>>nativeDecodeAsset()來完成decode,

無需再使用java層的createBitmap,從而節省了java層的空間。

方法2:對圖片采用軟引用,及時地進行recycle()操作

雖然,系統能夠確認Bitmap分配的內存最終會被銷毀,但是由於它占用的內存過多,所以很可能會超過java堆的限制。因此,在用完Bitmap時,要及時的recycle掉。recycle並不能確定立即就會將Bitmap釋放掉,但是會給虛擬機一個暗示:“該圖片可以釋放了”。

1SoftReference bitmap;

2bitmap =newSoftReference(pBitmap);

3

4if(bitmap !=null){

5if(bitmap.get() !=null&& !bitmap.get().isRecycled()){

6bitmap.get().recycle();

7bitmap =null;

8}

9}

方法3:單個頁面,橫豎屏切換N次後 OOM

1.看看頁面布局當中有沒有大的圖片,比如背景圖之類的。去除xml中相關設置,改在程序中設置背景圖(放在onCreate()方法中):

1Drawable bg = getResources().getDrawable(R.drawable.bg);

2XXX.setBackgroundDrawable(rlAdDetailone_bg);

在Activity destory時注意,bg.setCallback(null);防止Activity得不到及時的釋放。

2.跟上面方法相似,直接把xml配置文件加載成view再放到一個容器裡,然後直接調用 this.setContentView(View view);避免xml的重復加載。

方法4:在頁面切換時盡可能少地重復使用一些代碼。比如:重復調用數據庫,反復使用某些對象等等.....

方法5:Android堆內存也可以自己定義大小和優化Dalvik虛擬機的內存

--參考資料:http://blog.csdn.net/wenhaiyan/article/details/5519567

注意:若使用這種方法:project build target只能選擇 <= 2.2 版本,否則編譯將通不過。所以不建議用這種方式。

五、Android中內存洩露監測

內存監測工具DDMS --> Heap

 

\

 

使用方法比較簡單:

·選擇DDMS視圖,並打開Devices視圖和Heap視圖

·點擊選擇要監控的進程,比如:上圖中我選擇的是system_process

·選中Devices視圖界面上的"update heap"圖標

·點擊Heap視圖中的"Cause GC"按鈕(相當於向虛擬機發送了一次GC請求的操作)

在Heap視圖中選擇想要監控的Type,一般我們會觀察dataobject的total size的變化,正常情況下total size的值會穩定在一個有限的范圍內,也就說程序中的代碼良好,沒有造成程序中的對象不被回收的情況。如果代碼中存在沒有釋放對象引用的情況,那麼data object的total size在每次GC之後都不會有明顯的回落,隨著操作次數的增加而total size也在不斷的增加。(說明:選擇好data object後,不斷的操作應用,這樣才可以看出total size的變化)。如果totalsize確實是在不斷增加而沒有回落,說明程序中有沒有被釋放的資源引用。那麼我們應該怎麼來定位呢?

Android中內存洩露定位

通過DDMS工具可以判斷應用程序中是否存在內存洩漏的問題,那又如何定位到具體出現問題的代碼片段,最終找到問題所在呢?內存分析工具MAT Memory Analyzer Tool解決了這一難題。MAT工具是一個Eclipse 插件,同時也有單獨的RCP 客戶端,MAT工具的解析文件是.hprof,這個文件存放了某進程的內存快照。MAT工具定位內存洩漏具體位置的方法如下:

① 生成.hprof文件。Eclipse中生成.hprof文件的方法有很多,不同Android版本中生成.hprof的方式也稍有差別,但它們整體思路是一樣的。我們在DDMS界面選中想要分析的應用進程,在Devices視圖界面上方的一行圖標按鈕中,同時選中“Update Heap”和“Dump HPROF file”兩個按鈕,這時DDMS將會自動生成當前選中進程的.hprof文件。

② 將.hprof 文件導入到MAT工具中,MAT工具會自動解析並生成報告,點擊“Dominator Tree”按鈕,並按包分組,選擇已定義的包類點右鍵,在彈出的菜單中選擇List objects﹥With incoming references,這時會列出所有可疑的類。右鍵點擊某一項,並選擇Path to GC Roots﹥excludeweak/soft references,MAT工具會進一步篩選出跟程序相關的所有內存洩漏的類。這樣就可以追蹤到某一個產生內存洩漏的類的具體代碼中。

使用MAT內存分析工具查找內存洩漏的根本思路是找到哪個類的對象的引用沒有被釋放,然後分析沒有被釋放的原因,最終定位到代碼中哪些片段存在著內存洩漏。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved