【转】Android Out Of Memory(OOM)的详细研究

来源:互联网 发布:淘宝优惠券显示不了 编辑:程序博客网 时间:2024/05/22 08:46
Android Out Of Memory(OOM) 的詳細研究


基於Android開發應用時,可能會挺時常出現Out Of Memory 異常.在Android中,一個Process 只能使用16M記憶體,要是超過了這個限定就會跳出這個異常。這樣就要求我們要時刻想著開釋資源。Java的回收工作是交給GC的,如何讓GC能即時的回收已經不是用的物件,這個裏面有許多技巧,各人可以google一下。
因為總記憶體的施用超過16M而引起OOM的情況,非常簡單,我就不繼續展開說。值當注意的是Bitmap在不用時,肯定是要recycle,不然OOM是非常容易出現的。本文想跟各人一起討論的是另外一種情況:明明還有許多記憶體,但是發生OOM了。這類情況時常出現在生成Bitmap的時候。有興趣的可以試一下,在一個函數裏生成一個13m 的int陣列。 在該函數結束後,按理說這個int陣列應該已經被開釋了,或者說可以開釋,這個13M的空間應該可以空出來,這個時候要是你繼續生白手起家的百萬富翁成一個10M的int陣列是沒有問題的,反而生成一個4M的Bitmap就會跳出OOM。這個就奇怪了,為啥子10M的int夠空間,反而4M的Bitmap不敷呢?
這個問題困擾好久,在網上,國外各大論壇搜刮了好久,一般關於OOM的解釋和解決方法都是,如何讓GC儘快回收的代碼風格之類,並沒有現實的支出上面所說的情況的根源。直到昨天在一個老外的blog上終於看到了這方面的解釋,我理解後歸納如下: 


在Android中:
一個進程的記憶體可以由2個部門組成:java 施用記憶體 ,C 施用記憶體 ,這兩個記憶體的和必需小於16M,不然就會出現各人熟悉的OOM,這個就是熬頭種OOM的情況。
 
越發奇怪的是這個:一朝記憶體分配給Java後,以後這塊記憶體縱然開釋後,也只能給Java的施用,這個估計跟java虛擬機裏把記憶體分成好幾塊進行緩存的原因有關,反正C就別想用到這塊的記憶體了,所以要是Java突然佔用了一個大塊記憶體,縱然很快開釋了: 


  C能施用的記憶體 = 16M - Java某一瞬間占在校大學生創業點子用的最大記憶體。
而Bitmap的生成是路程經過過程malloc進行記憶體分配的,佔用的是C的記憶體,這個也就說明了,上面所說的的4MBitmap無法生成的原因,因為在13M被Java用過後,剩下C能用的只有3M了。


Android學習之記憶體管理機制
 
很多開發者都是從J2ME或J2EE上過來的,對於記憶體的使用和理解並不是很到位,Android開發網本次給大家一些架構上的指導,防止出現豆腐渣工程的出現。Android作為以Java語言為主的智慧平臺對於我們開發一些高性能和品質的軟體來說瞭解Android程式記憶體管理機制是必須的。 Android的Dalvik VM在基礎方面和Sun JVM沒有什麼大的區別僅僅是位元組碼的優化,我們要知道什麼時候用gc什麼時候用recycle以及到底用不用finalization,因為Java對記憶體的分配只需要new開發者不需要顯示的釋放記憶體,但是這樣造成的記憶體洩露問題的幾率反而更高。
對於常規開發者而言需要瞭解 Java的四種引用方式,比如強引用,軟引用,弱引用以及虛引用。一些複雜些的程式在長期運行很可能出現類似OutOfMemoryError的異常。
 
並不要過多的指望gc,不用的物件可以顯示的設置為空,比如obj=null,這裏Android123提示大家,java的gc使用的是一個有向圖,判斷一個物件是否有效看的是其他的物件能到達這個物件的頂點,有向圖的相對於鏈表、二叉樹來說開銷是可想而知。
 
Android為每個程式分配的對記憶體可以通過Runtime類的totalMemory() freeMemory() 兩個方法獲取VM的一些記憶體資訊,對於系統heap記憶體獲取,可以通過Dalvik.VMRuntime類的getMinimumHeapSize() 方法獲取最小可用堆記憶體,同時顯示釋放軟引用可以調用該類的gcSoftReferences() 方法,獲取更多的運行記憶體。
 
對於多線程的處理,如果併發的線程很多,同時有頻繁的創建和釋放,可以通過concurrent類的線程池解決線程創建的效率瓶頸。
 
不要在迴圈中創建過多的本地變數。
有關Android和Java的系統性能分析,Android123將在以後的文章中詳細講述如何調試Java分析記憶體洩露以及Android上的gdb調試器分析得出記憶體性能改進。


Android有效解決載入大圖片時記憶體溢出的問題
 
儘量不要使用setImageBitmap或setImageResource或BitmapFactory.decodeResource來設置一張大圖,因為這些函數在完成decode後,最終都是通過java層的createBitmap來完成的,需要消耗更多記憶體。


因此,改用先通過BitmapFactory.decodeStream方法,創建出一個bitmap,再將其設為ImageView的 source,decodeStream最大的秘密在於其直接調用JNI>>nativeDecodeAsset()來完成decode,無需再使用java層的createBitmap,從而節省了java層的空間。
如果在讀取時加上圖片的Config參數,可以跟有效減少載入的記憶體,從而跟有效阻止拋out of Memory異常,另外,decodeStream直接拿的圖片來讀取位元組碼了, 不會根據機器的各種解析度來自動適應,使用了decodeStream之後,需要在hdpi和mdpi,ldpi中配置相應的圖片資源,否則在不同解析度機器上都是同樣大小(圖元點數量),顯示出來的大小就不對了。


另外,以下方式也大有幫助:
InputStream is = this.getResources().openRawResource(R.drawable.pic1);
BitmapFactory.Options options=new BitmapFactory.Options();
options.inJustDecodeBounds = false;
options.inSampleSize = 10;   //width,hight設為原來的十分一
Bitmap btp =BitmapFactory.decodeStream(is,null,options);
if(!bmp.isRecycle() ){
     bmp.recycle()   //回收圖片所占的記憶體
     system.gc()  //提醒系統及時回收
}


以下奉上一個方法:
 1.  
 7. public static Bitmap readBitMap(Context context, int resId){  
 8.     BitmapFactory.Options opt = new BitmapFactory.Options();  
 9.     opt.inPreferredConfig = Bitmap.Config.RGB_565;   
10.     opt.inPurgeable = true;  
11.     opt.inInputShareable = true;  
12.        //獲取資源圖片  
13.     InputStream is = context.getResources().openRawResource(resId);  
14.         return BitmapFactory.decodeStream(is,null,opt);  
15. }


Android記憶體溢出的解決辦法


昨天在模擬器上給gallery放入圖片的時候,出現java.lang.OutOfMemoryError: bitmap size exceeds VM budget 異常,圖像大小超過了RAM記憶體。模擬器RAM比較小,只有8M記憶體,當我放入的大量的圖片(每個100多K左右),就出現上面的原因。由於每張圖片先前是壓縮的情況,放入到Bitmap的時候,大小會變大,導致超出RAM記憶體,具體解決辦法如下:


通過上面的方式解決了,但是這並不是最完美的解決方式。


通過一些瞭解,得知如下:
   //解決載入圖片 記憶體溢出的問題
   //Options 只保存圖片尺寸大小,不保存圖片到記憶體
   BitmapFactory.Options opts = new BitmapFactory.Options();


   //縮放的比例,縮放是很難按準備的比例進行縮放的,其值表明縮放的倍數,
   // SDK中建議其值是2的指數值,值越大會導致圖片不清晰
   opts.inSampleSize = 4;
   Bitmap bmp = null;
   bmp = BitmapFactory.decodeResource(getResources(), mImageIds[position],opts);




...


   //回收
   bmp.recycle();
 
優化Dalvik虛擬機的堆記憶體分配
對於Android平臺來說,其託管層使用的Dalvik Java VM從目前的表現來看還有很多地方可以優化處理,比如我們在開發一些大型遊戲或耗資源的應用中可能考慮手動干涉GC處理,使用 dalvik.system.VMRuntime類提供的setTargetHeapUtilization方法可以增強程式堆記憶體的處理效率。當然具體原理我們可以參考開源工程,這裏我們僅說下使用方法: private final static float TARGET_HEAP_UTILIZATION = 0.75f; 在程式onCreate時就可以調用 VMRuntime.getRuntime().setTargetHeapUtilization(TARGET_HEAP_UTILIZATION); 即可。




Android堆記憶體也可自己定義大小
對於一些Android專案,影響性能瓶頸的主要是Android自己記憶體管理機制問題,目前手機廠商對RAM都比較吝嗇,對於軟體的流暢性來說RAM對性能的影響十分敏感,除了 優化Dalvik虛擬機的堆記憶體分配外,我們還可以強制定義自己軟體的對記憶體大小,我們使用Dalvik提供的 dalvik.system.VMRuntime類來設置最小堆記憶體為例:
  //設置最小heap記憶體為6MB大小。當然對於記憶體吃緊來說還可以通過手動干涉GC去處理
  private final static int CWJ_HEAP_SIZE = 6* 1024* 1024 ;
  VMRuntime.getRuntime().setMinimumHeapSize(CWJ_HEAP_SIZE);
bitmap 設置圖片尺寸,避免 記憶體溢出 OutOfMemoryError的優化方法
★android 中用bitmap 時很容易記憶體溢出,報如下錯誤:Java.lang.OutOfMemoryError : bitmap size exceeds VM budget
●  主要是加上這段:
   BitmapFactory.Options options = new BitmapFactory.Options();
   options.inSampleSize = 2;
● eg1:(通過Uri取圖片)
   private ImageView preview;
   BitmapFactory.Options options = new BitmapFactory.Options();
   options.inSampleSize = 2;//圖片寬高都為原來的二分之一,即圖片為原來的四分之一
   Bitmap bitmap = BitmapFactory.decodeStream(cr.openInputStream(uri), null, options);
   preview.setImageBitmap(bitmap);
 
以上代碼可以優化記憶體溢出,但它只是改變圖片大小,並不能徹底解決記憶體溢出。
● eg2:(通過路徑去圖片)
   private ImageView preview;
   private String fileName= "/sdcard/DCIM/Camera/2010-05-14 16.01.44.jpg";
   BitmapFactory.Options options = new BitmapFactory.Options();
   options.inSampleSize = 2;//圖片寬高都為原來的二分之一,即圖片為原來的四分之一
   Bitmap b = BitmapFactory.decodeFile(fileName, options);
   preview.setImageBitmap(b);
   filePath.setText(fileName);
★Android 還有一些性能優化的方法:
● 首先記憶體方面,可以參考 Android堆記憶體也可自己定義大小 和 優化Dalvik虛擬機的堆記憶體分配


● 基礎類型上,因為Java沒有實際的指標,在敏感運算方面還是要借助NDK來完成。Android123提示遊戲開發者,這點比較有意思的是Google 推出NDK可能是幫助遊戲開發人員,比如OpenGL ES的支持有明顯的改觀,本地代碼操作圖形介面是很必要的。


● 圖形物件優化,這裏要說的是Android上的Bitmap物件銷毀,可以借助recycle()方法顯示讓GC回收一個Bitmap物件,通常對一個不用的Bitmap可以使用下面的方式,如
  if(bitmapObject.isRecycled()==false) //如果沒有回收
  bitmapObject.recycle();
● 目前系統對動畫支援比較弱智對於常規應用的補間過渡效果可以,但是對於遊戲而言一般的美工可能習慣了GIF方式的統一處理,目前Android系統僅能預覽GIF的第一幀,可以借助J2ME中通過線程和自己寫解析器的方式來讀取GIF89格式的資源。


● 對於大多數Android手機沒有過多的物理按鍵可能我們需要想像下了做好手勢識別 GestureDetector 和重力感應來實現操控。通常我們還要考慮誤操作問題的降噪處理。
1 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 淘宝卖家填写虚假物流信息怎么办 淘宝店铺的浏览量越来越少怎么办 网上充手机话费充错了怎么办 夜神模拟器上陌陌的位置不对怎么办 如果在大庭广众之下放了个屁怎么办 淘宝分销上传宝贝被系统下架怎么办 酷狗喜欢歌单里面的歌都没了怎么办 苹果手机下载不了微信缓冲怎么办 登陆微信提示版本过低登不了怎么办 苹果手机微信版本过低登不上怎么办 微信小程序显示微信版本过低怎么办 三星手机登微信显示版本过低怎么办 微信版本低无法登录无法升级怎么办 手机淘宝五应用界面无法打开怎么办 入住淘宝主播没有微博粉丝怎么办 手机淘宝领金币怎么没有了怎么办 淘宝荬家缺货对付款买家怎么办 淘宝买家确认收货后申请退款怎么办 淘宝东西失效了但付过款了怎么办 淘宝图片被投诉盗图怎么办原图没了 淘宝退款申请不小心撤销了怎么办 淘宝不小心点了撤销退款怎么办 淘宝上退款不小心撤销了怎么办 新店淘宝卖家想开通直播怎么办 想开通淘宝直播却没有粉丝怎么办 淘宝店家拒绝给我改地址怎么办0 淘宝被投诉卖假申诉失败怎么办 要退保证金但是有人买东西了怎么办 手机淘宝上我是商家停止服务怎么办 淘宝上买的电脑商家关闭了怎么办 淘宝链接复制链链接发不出去怎么办 淘宝卖家填错地址货已经发了怎么办 拼多多没交保证金无法提现怎么办 信用卡被别人刷了没证据怎么办 我在淘宝开个店卖衣服没人买怎么办 收货地址填错了卖家已发货怎么办 淘宝店上的供货商不发货怎么办 在淘宝买的花退到卖家后死了怎么办 在淘宝买东西付款开通了花呗怎么办 淘宝网投诉卖家也不处理问题怎么办 淘宝图片空间上传不了大图片怎么办