Android Memory Management, OutOfMemoryError

A

   Android框架强制每个进程的24 MB内存限制。在一些旧的设备,如在G1,限制为16 MB 更低,更重要的是,由位图使用的内存限制。处理图像的应用程序,它是很容易达到此限制,并获得与OOM 异常死亡 的过程:E / dalvikvm堆(12517):1048576字节外部分配这个 过程中过大的E / GraphicsJNI(12517): VM将不会让我们分配1048576字节 / AndroidRuntime(12517):关闭VM / dalvikvm(12517):主题ID = 1:线程未捕获的异常退出(集团= 0x4001d7f0 ) E / AndroidRuntime(12517):致命异常:主要 电子/ AndroidRuntime(12517):java.lang.OutOfMemoryError:位图的大小超过VM的预算 ,这个限制是低得离谱 。设备,像512MB的物理RAM的Nexus之一,设置每个进程的前台活动只有5%的RAM的内存限制是一个愚蠢的错误 。但无论如何,事情是如何和我们生活-即找到如何解决它。

远远超过限制的内存分配方式有两种 :

  一种方法是从本机代码分配内存 。使用NDK(本地开发工具包)和JNI,它可能从C级(如的malloc / free或新建/删除)分配内存,这样的分配是不计入对24 MB的限制 。这是真的,从本机代码分配内存是为从Java方便,但它可以被用来存储在RAM中的数据(即使图像数据)的一些大金额 。

  另一种方式,其中的作品以及图像的,是使用OpenGL的纹理-纹理内存不计入限制 ,要查看您的应用程序确实分配多少内存可以使用android.os.Debug.getNativeHeapAllocatedSize( ),可以使用上面介绍的两种技术的Nexus之一,我可以轻松地为一个单一的前台进程分配300MB - 10倍以上的默认24 MB的限制 ,从上面来看使用navtive代码分配内存是不在24MB的限制内的(开放的GL的质地也是使用navtive代码分配内存的) 。

  每个 android 平台内存限制不一样,从最开始的 16M 到 24M,以及后来的 32M,64M,或许以后会更大。

  那如何获取单个 app 内存限制大小呢?

  class : ActivityManager

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

  当然,ActivityManager 不单单限与此,许多对 android 程序管理的工具,都来源与此,或者从这里进行扩展。 

 


android不同设备单个进程可用内存是不一样的,可以查看/system/build.prop文件。

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

heapsize参数表示单个进程可用的最大内存,但如果存在如下参数:

dalvik.vm.heapgrowthlimit=48m表示单个进程内存被限定在48m,即程序运行过程中实际只能使用48m内存

android上的应用是java,当然需要虚拟机,而android上的应用是带有独立虚拟机的,也就是每开一个应用就会打开一个独立的虚拟机。这样设计的原因是可以避免虚拟机崩溃导致整个系统崩溃,但代价就是需要更多内存。以上这些设计确保了android的稳定性,正常情况下最多单个程序崩溃,但整个系统不会崩溃,也永远没有内存不足的提示出现。


 在Android中,一个Process 只能使用16M内存(?),要是超过了这个限定就会跳出这个异常

  For Android specific we should use the 'recycle' method rather than 'gc', because 'recycle' will free the memory at the same time, but calling 'gc' doesn't guaranty to run and free the memory for same time(if it is not too critical, we should not call gc in our code) and results can very every time.
One more thing using 'recycle' is faster than the 'gc' and it improves the performance.

即:bitmap.recycle();

  biamap=null;

效果要好于

  biamap=null;

  system.gc();

通过DDMS中的Heap选项卡监视内存情况:

1.Heap视图中部有一个Type叫做data object,即数据对象,也就是我们的程序中大量存在的类类型的对象。

2.在data object一行中有一列是“Total Size”,其值就是当前进程中所有Java数据对象的内存总量。

如果代码中存在没有释放对象引用的情况,则data object的Total Size值在每次GC后不会有明显的回落,随着操作次数的增多Total Size的值会越来越大,
  直到到达一个上限后导致进程被kill掉。

B  今天刚遇到的情况:发现gridview的getview中使用

    @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            final View GridItem = mInflater.inflate(R.layout.store_catg_item,null, false);
       TextView text = (TextView) GridItem.findViewById(R.id.store_catg_item_text);
            ImageView cover = (ImageView) GridItem.findViewById(R.id.store_catg_item_cover);
            Bitmap coverimg = ImageUtilities.getCachedCover(magaList
                    .get(position).id+ReaderConfigures.THUMB_SUFFIX_PLANE);
            String title;
            if(isCatg){
                title= magaList.get(position).category;
                text.setText(title.toUpperCase());
            }else{
                title= magaList.get(position).pubname;
                text.setVisibility(View.INVISIBLE);
            }
            GridItem.setTag(title);
            cover.setImageBitmap(coverimg);
            return GridItem;
        }

 

滑动时内存会不断涨,直到OutOfMemory,使用Holder后便不会发生该请况,具体原因未仔细查找,标记一下。

 1.对于常规开发者而言需要了解 Java的四种引用方式,比如强引用,软引用,弱引用以及虚引用。一些复杂些的程序在长期运行很可能出现类似OutOfMemoryError的异常。

2.并不要过多的指望gc,不用的对象可以显示的设置为空,比如obj=null,java的gc使用的是一个有向图,判断一个对象是否有效看的是其他的对象能到达这个对象的顶点,有向图的相对于链表、二叉树来说开销是可想而知。

3.Android为每个程序分配的对内存可以通过Runtime类的totalMemory() freeMemory() 两个方法获取VM的一些内存信息,

Runtime.getRuntime().freeMemory();

Formatter.formatFileSize(BaseActivity.baseContext,Runtime.getRuntime().freeMemory()));//格式化输出

对于系统heap内存获取,可以通过Dalvik.VMRuntime类的getMinimumHeapSize() 方法获取最小可用堆内存,同时显示释放软引用可以调用该类的gcSoftReferences() 方法,获取更多的运行内存。

4.对于多线程的处理,如果并发的线程很多,同时有频繁的创建和释放,可以通过concurrent类的线程池解决线程创建的效率瓶颈。

5. 不要在循环中创建过多的本地变量。

c

  The default heap size of android3.0 is 48M.Large background pictrue,button icon and the other pictrues used as ui all consume memory,and even if you have entered another activity,the resource of the previous activity still be keeped.So you had better not use the big pictrue in UI.

  在onDestroy中会用((BitmapDrawable)mBtn.getBackground()).setCallback(null)清理背景图。按道理来说图片资源应该已经清理掉了的。仔细看Bitmap的源代码,它其实起的作用是销毁java对象BitmapDrawable,而android为了提高效率,Bitmap真正的位图数据是在ndk中用c写的,所以用setCallback是不能销毁位图数据的,应该调用Bitmap的recycle()来清理内存。在onDestroy加上((BitmapDrawable)mBtn.getBackground()).getBitmap().recycle(),这样跑下来,内存情况很理想,不管在哪个activity中,使用的资源仅仅是当前activity用到的,就不会象之前到最后一个activity的时候,所有之前使用的资源都累积在内存中。

  但新的问题又出现了,当返回之前的activity时,会出现“try to use a recycled bitmap"的异常。这真是按了葫芦起了瓢啊,内心那个沮丧。。。没办法,继续分析。看来是后加上recycle引起的, 位图肯定在内存中有引用,在返回之前的activity时,因为位图数据其实已经被销毁了,所以才造成目前的情况。在看了setBackgroundResource的源码以后,恍然大悟,android对于直接通过资源id载入的资源其实是做了cache的了,这样下次再需要此资源的时候直接从cache中得到,这也是为效率考虑。但这样做也造成了用过的资源都会在内存中,这样的设计不是很适合使用了很多大图片资源的应用,这样累积下来应用的内存峰值是很高的。看了sdk后,我用:

Bitmap bm = BitmapFactory.decodeResource(this.getResources(), R.drawable.splash);
BitmapDrawable bd = new BitmapDrawable(this.getResources(), bm);

mBtn.setBackgroundDrawable(bd);

来代替mBtn.setBackgroundResource(R.drawable.splash)。

销毁的时候使用:

BitmapDrawable bd = (BitmapDrawable)mBtn.getBackground();

mBtn.setBackgroundResource(0);//别忘了把背景设为null,避免onDraw刷新背景时候出现used a recycled bitmap错误

bd.setCallback(null);
bd.getBitmap().recycle();

这样调整后,避免了在应用里缓存所有的资源,节省了宝贵的内存,而其实这样也不会造成太大效率问题,毕竟重新载入资源是非常快速,不会对性能造成很严重的影响,在xoom里我没有感受到和之前有什么区别。

总之,在android上使用大量位图是个比较痛苦的事,内存限制的存在对应用是个很大的瓶颈。但不用因噎费食,其实弄明白了它里面的机制,应用可以突破这些限制的。这只是其中的一种处理方法,还可以考虑BitmapFactory.Options的inSampleSize来减少内存占用。

浏览大图的应用,可以使用JNI的方法加载图片

posted @ 2012-04-25 09:25  Qiengo  阅读(5236)  评论(0编辑  收藏  举报