Android應用中OOM

1.什麼是OOM?

03-21 21:05:28.771: E/dalvikvm-heap(13316): Out of memory on a 10485776-byte allocation. 03-21 21:05:28.779: E/AndroidRuntime(13316): java.lang.OutOfMemoryError

這幾句的意思是,咱們程序申請須要10485776byte太大了,虛擬機沒法知足咱們,羞愧的shutdown自殺了。這個現象一般出如今用到不少圖片或者很大圖片的APP開發中。通俗講就是當咱們的APP須要申請一塊內存來裝圖片的時候,系統以爲咱們的APP所使用的內存已經夠多了。即便它有1G的空餘內存,它不一樣意給個人APP更多的內存裏,而後即便系統立刻拋出OOM錯誤,而程序沒有捕捉該錯誤,故彈框崩潰了。 

java

2.爲何會有OOM?

由於android系統的app的每一個進程或者每一個虛擬機有個最大內存限制,若是申請的內存資源超過這個限制,系統就會拋出OOM錯誤。跟整個設備的剩餘內存沒太大關係。好比比較早的android系統的一個虛擬機最多16M內存,當一個app啓動後,虛擬機不停的申請內存資源來裝載圖片,當超過內存上限時就出現OOM。Android系統的APP內存限制怎麼肯定? 
android

2.1 Android的APP內存組成:

        APP內存由 dalvik內存 和 native內存 2部分組成,dalvik也就是java堆,建立的對象就是就是在這裏分配的,而native是經過c/c++方式申請的內存,Bitmap就是以這種方式分配的。(android3.0之後,系統都默認經過dalvik分配的,native做爲堆來管理)。這2部分加起來不能超過android對單個進程,虛擬機的內存限制。 每一個手機的內存限制大小是多少?c++

        ActivityManager activityManager = (ActivityManager)context.getSystemService(Context.ACTIVITY_SERVICE)         activityManager.getMemoryClass();


       以上方法會返回以M爲單位的數字,不一樣的系統平臺或設備上的值都不太同樣,好比HTC默認24M, Galaxy36M, emulator-2.3 24M,等等。個人moto xt681是42M。3 上面取到是虛擬機的最大內存資源。        而對於head堆的大小限制,能夠查看 /system/build.prop 文件。算法

       dalvik.vm.heapstartsize = 5m        dalvik.vm.heapgrowthlimit=48m        dalvik.vm.heapsize=256m

 注: heapsize參數表示單個進程heap可用的最大內存,但若是存在如下參數"dalvik.vm.headgrowthlimit =48m"表示單個進程heap內存被限定在48m,即程序運行過程實際只能使用48m內存。 
shell

2.2 爲何android系統設定APP的內存限制?

        1   要使開發者內存使用更爲合理。 限制每一個應用的可用內存上限,能夠放置某些應用程序惡意或者無心的使用過多的內存。而致使其它應用沒法正常運行。Android是有多進程的,若是一個進程(就是一個應用)耗費過多的內存,其餘應用就沒法運行了。由於有了限制,使得開發者必須好好利用有限資源,優化資源的使用。 
        2  屏幕顯示內容有限,內存足夠便可。 即便有萬千圖片千萬數據須要使用到,但在特定時刻須要展現給用戶看的老是有限的,由於屏幕顯示就那麼大,上面能夠放的信息就是頗有限的。大部分信息都是處於準備顯示狀態,因此不必給予太多heap內存。也就是說出現 OOM現象,絕大部分緣由是咱們的程序設計上有問題,須要優化 。優化方法不少,好比經過時間換空間,不停的加載要用的的圖片,不停的回收不用的圖片,把大圖片解析成適合手機屏幕大小的圖片等。 
        3   多APP多個虛擬機davlik的限制須要。 android上的app使用獨立虛擬機,每開一個應用就會打開至少一個獨立的虛擬機。這樣能夠避免虛擬機崩潰致使整個系統崩潰,同時代價就是須要浪費更多的內存。這樣設計保證了android的穩定性。 
編程

2.3 不是GC自動回收資源麼,爲何還會OOM?

       Android不是用GC會自動回收資源麼,爲何app的那些不用的資源不回收呢?        Android的gc會按照特定的算法回收程序不用的內存資源,避免app的內存申請約積越多,可是gc通常回收的資源是那些無主的對象內存或者軟飲用的資源,或者更軟引用的引用資源。好比:        緩存

       Bitmap bt = BitmapFactory.decodeResource(this.getResources(), R.drawable.splash); //此時的圖片資源是強引用,是有主的資源。        bt = null; //此時這個圖片資源就是無主的了。gc心情號的時候就會去回收它。        SoftReference<Bitmap> softRef = new SoftReference<Bitmap>(bt);        bt = null;        其餘代碼...


      當程序申請不少內存資源時,gc有可能會釋放softref引用的這個圖片內存。bt=softRef.get(),此時可能獲得的是null,須要從新加載圖片。       固然這也說明了用軟引用圖片資源的好處,就是gc會自動根據須要釋放資源,必定程度上避免OOM。 
TIPS:編程要養成習慣,不用的對象設置爲null。其實更好的是,不用的圖片直接recycle。由於經過設置null讓gc來回收,有時候仍是會來不及。 
網絡

2.4 怎麼查看APP內存分配狀況?

       1  經過DDMS中的heap選項卡監視內存狀況 :         Heap視圖中部有一個叫作data object, 即數據對象,也就是咱們的程序中大量存在的類類型的對象。         在data object一行中有一列是「Total Size」, 其值就是當前進程中全部Java數據對象的內存總量。若是代碼中存在沒有釋放對象引用的狀況,則data object的「Total Size」值在每次gc後不會有美線的回落。隨着操做次數的增長「Total Size」的值會愈來愈大。直到到達一個上限 後致使進程被kill掉。         2  在App裏面咱們能夠經過totalMemory與freeMemory:數據結構

      Runtime.getRuntime().freeMemory()       RUntime.getRuntime().totalMemory()

        3  adb shell dumpsys meminfo com.android.demo 

多線程

3. 常見避免OOM的幾個注意點:

      3.1 適當調整圖像大小 。由於手機屏幕尺寸有限,分配給圖像的顯示區域有限,尤爲對於超大圖片,加載自網絡或者sd卡,圖片文件說起達到幾M或者十幾個M的: 加載到內存前,先算出該bitmap的大小,而後經過適當調節採樣率使得加載的圖片恰好,或稍大捷克在手機屏幕上顯示就滿意了:         

 BimtapFactory.Option opts = new  BitampFactory.Option();          opts.inJustDecodeBounds = true;          opts.inSampleSize=computeSample(opts, minSideLength, maxNumOfPixels); // Android 提供了一種動態計算的方法 computeSampleSize          opts.inJustDecodeBounds = false;          try{                 return BitmapFactory.decodeFile(imageFile, opts);          } catch(OutOfMemoryError err){  }

      3.2 圖像緩存 。在listview或Gallery等控件中一次性加載大量圖片時,只加載屏幕顯示的資源,還沒有顯示的不加載,移出屏幕的資源及時釋放,採用強引用+軟引用2級緩存,提升加載性能。緩存圖像到內存,採用軟引用緩存到內存,而不是在每次使用的時候都重新加載到內存。       3.3 採用低內存佔用量的編碼方式 。好比Bitmap.Config.ARGB_4444比Bitmap.Config.ARGB_8888更省內存。       3.4 及時回收圖像 。若是引用了大量的Bitmap對象,而應用又不須要同時顯示全部圖片。能夠將暫時不用到的Bitmap對象及時回收掉。對於一些明確直到圖片使用狀況的場景能夠主動recycle回收      App的啓動splash畫面上的圖片資源,使用完就recycle。對於幀動畫,能夠加載一張,畫一張,釋放一張。       3.5 不要在循環中建立過多的本地變量 。慎用static,用static來修飾成員變量時,該變量就屬於該類,而不是該類實例,它的生命週期是很長的。若是用它來引用一些內存佔用太多的實例,這時候就要謹慎對待了。       3.6 自定義堆內存分配大小 。優化Dalvik虛擬機的堆內存分配。   

 public class ClassName{                 private static Context mContext;                    // 省略             }




4. App使用圖片時避免OOM的幾種方式:

     4.1  直接null或recycle       對於app裏使用的大量圖片,採用方式:使用時加載,不顯示時直接置null或recycle。       這樣處理是個好習慣,記本能夠杜絕OOM,可是缺憾是代碼多了,可能會忘記某些資源recycle。       而有些狀況下會出現特定圖片反覆加載,釋放,再加載等,低效率的事情。       4.2 簡單經過SoftReference引用方式管理圖片資源       建個SoftReference的hashmap       使用圖片時先查詢這個hashmap是否有softreference, softreference裏的圖片是否爲空,       若是爲空就加載圖片到softreference並加入hashmap。       無需再代碼裏顯式的處理圖片的回收與釋放,gc會自動處理資源的釋放。       這種方式處理起來簡單實用,能必定程度上避免前一種方法反覆加載釋放的低效率。但還不夠優化。       4.3 強引用+軟引用二級緩存       Android示範程序ImageDownloader.java, 使用了一個二級緩存機制。就是有一個數據結構直接持有解碼成功的Bitmap對象引用,同時使用一個二級緩存數據結構保持淘汰的Bitmap的softreference對象,因爲softreference對象的特殊性,系統會再須要內存的時候首先將softreference持有的對象釋放掉,也就是說當vm發現可用的內存較少須要出發gc的時候,二級緩存中的bitmap對象將被回收,而持有一級緩存的bitmap對象用於顯示。               其實這個解決方案最爲關鍵的一點是使用了一個比較合適的數據結構,那就是LinkedHashMap類型來進行一級緩存Bitmap的容器。因爲LinkeHashMap的特殊性,咱們能夠控制其內存存儲對象的個數而且將不在使用的對象從容器中移除,放到softreference二級緩存裏,咱們能夠在一級緩存中一致保存最近被訪問到的bitmap對象,而已經被訪問過的圖片在LinkedHashMap的容量超過咱們預設值時將會把容器中存在的時間最長的對象移除,這個時候我麼能夠將被移除的LinkedHashMap中的放到二級緩存容器,而二級緩存中的對象管理就交給系統來作了,當系統須要gc時就會首先回收二級緩存容器的Bitmap對象了。                    在獲取圖片對象時候先從一級緩存容器中查找,若是有對應對象並可用直接返回,若是沒有的話從二級緩存中查找對應的SoftReference, 判斷SoftReference對象持有的Bitmap是否可用,可用直接返回,不然返回空。若是二級緩存都找不到圖片,那就直接加載圖片資源。      4, LruCache  + sd的緩存方式 

5. 兩種容易OOM的場景建議:

   5.1 網絡下載大量圖片     好比微博客戶端: 多線程異步網絡,小兔直接用LRUCache+SoftRef+Sd,大圖按需下載:         5.2 對於須要加載很是多條目信息的listview,gridview等的狀況     在adapter的getView函數裏有個convertView參數,告知你是否有可重用的view對象。 若是不使用convertView的話,每次調用getView時每次都會從新建立view,這樣以前的view可能還沒銷燬,加之不斷的新建view勢必會形成內存劇增,從而致使OOM。另外在重用convertView時,裏面原有的圖片等資源就會變成無主的了。    這裏Google官方推薦使用:「convertview+靜態類viewholder」    官方給出解釋是:     a 重用緩存convertView傳遞給getView()方法來避免填充沒必要要的視圖。     b 使用ViewHolder模式來避免沒有必要的調用findViewById;由於太多的findViewById也會影響性能。 
附ViewHolder類的做用 :ViewHolder模式經過在getView方法返回的視圖的標籤(tag)中存儲一個數據結構。這個數據結構包含了指向咱們要綁定數據的視圖的引用,從而避免每次調用getView()的時候調用findViewById(); 

6 申請超過內存限制的內存分配方式:

   6.1 從Native C分配內存。 使用NDK(本地開發工具包)和JNI, 它可能從C級(如malloc/free或新建/刪除)分配內存,這樣的分配是不計入24MB的限制。這是真的,從本機代碼分配內存是爲了java方便,但它能夠被用來存儲在ram的數據(即便圖片數據)的一些打擊呢。    6.2 使用OpenGL的紋理。紋理內存不計入限制,要查看你的應用程序確實分配了多少內存可使用android.os.Debug.getNativeHeapAllocatedSize(), 可使用上面介紹的兩種技術的Nexus之一,我能夠輕鬆地爲一個單一的前臺進程分配300MB-10倍以上的默認24MB 的限制,從上面看來使用native代碼分配內存是不在24MB的限制內的(開放的GL的質地也是使用native代碼分配內存)。     可是,這兩個方法的風險就是,本地堆分配內存超過系統可用內存限制的話,一般都是直接崩潰。

相關文章
相關標籤/搜索