• Android Bitmap内存限制


    在编写Android程序的时候,我们总是难免会碰到OOM的错误,那么这个错误究竟是怎么来的呢?我们先来看一下这段异常信息:

    08-14 05:15:04.764: ERROR/dalvikvm-heap(264): 3528000-byte external allocation too large for this process. 
    08-14 05:15:04.764: ERROR/(264): VM won’t let us allocate 3528000 bytes 
    08-14 05:15:04.764: DEBUG/skia(264): — decoder->decode returned false 
    08-14 05:15:04.774: DEBUG/AndroidRuntime(264): Shutting down VM 
    08-14 05:15:04.774: WARN/dalvikvm(264): threadid=3: thread exiting with uncaught exception (group=0x4001b188) 
    08-14 05:15:04.774: ERROR/AndroidRuntime(264): Uncaught handler: thread main exiting due to uncaught exception 
    08-14 05:15:04.794: ERROR/AndroidRuntime(264): java.lang.OutOfMemoryError: bitmap size exceeds VM budget 
    08-14 05:15:04.794: ERROR/AndroidRuntime(264): at android.graphics.BitmapFactory.nativeDecodeAsset(Native Method) 
    08-14 05:15:04.794: ERROR/AndroidRuntime(264): at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:447) 
    08-14 05:15:04.794: ERROR/AndroidRuntime(264): at android.graphics.BitmapFactory.decodeResourceStream(BitmapFactory.java:323) 
    08-14 05:15:04.794: ERROR/AndroidRuntime(264): at android.graphics.BitmapFactory.decodeResource(BitmapFactory.java:346) 
    08-14 05:15:04.794: ERROR/AndroidRuntime(264): at android.graphics.BitmapFactory.decodeResource(BitmapFactory.java:372) 
    08-14 05:15:04.794: ERROR/AndroidRuntime(264): at com.xixun.test.HelloListView.onCreate(HelloListView.java:33) 
    08-14 05:15:04.794: ERROR/AndroidRuntime(264): at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1047) 
    08-14 05:15:04.794: ERROR/AndroidRuntime(264): at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2459) 
    08-14 05:15:04.794: ERROR/AndroidRuntime(264): at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2512) 
    08-14 05:15:04.794: ERROR/AndroidRuntime(264): at android.app.ActivityThread.access$2200(ActivityThread.java:119) 
    08-14 05:15:04.794: ERROR/AndroidRuntime(264): at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1863) 
    08-14 05:15:04.794: ERROR/AndroidRuntime(264): at android.os.Handler.dispatchMessage(Handler.java:99) 
    08-14 05:15:04.794: ERROR/AndroidRuntime(264): at android.os.Looper.loop(Looper.java:123) 
    08-14 05:15:04.794: ERROR/AndroidRuntime(264): at android.app.ActivityThread.main(ActivityThread.java:4363) 
    08-14 05:15:04.794: ERROR/AndroidRuntime(264): at java.lang.reflect.Method.invokeNative(Native Method) 
    08-14 05:15:04.794: ERROR/AndroidRuntime(264): at java.lang.reflect.Method.invoke(Method.java:521) 
    08-14 05:15:04.794: ERROR/AndroidRuntime(264): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:860) 
    08-14 05:15:04.794: ERROR/AndroidRuntime(264): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:618) 
    08-14 05:15:04.794: ERROR/AndroidRuntime(264): at dalvik.system.NativeStart.main(Native Method)

    从上面这段异常信息中,我们看到了一个OOM(OutOfMemory)错误,我称其为(OMG错误)。出现这个错误的原因是什么呢?为什么解码图像会出现这样的问题呢?关于这个问题,我纠结了一段时间,在网上查询了很多资料,甚至查看了Android Issues,确实看到了相关的问题例如Issue 3405Issue 8488,尤其Issue 8488下面一楼的回复,让我觉得很雷人啊:

    Comment <A href="http://code.google.com/p/android/issues/detail?id=8488#c1" name=c1 jQuery16105567010621656921="6"><FONT color=#0066cc>1</FONT></A> by <A href="http://code.google.com/u/@UxBXS1FTDhFGXgF4/" jQuery16105567010621656921="7"><FONT color=#0066cc>romain…@android.com</FONT></A>, May 23, 2010
    Your app needs to use less memory.

    当然我们承认不好的程序总是程序员自己错误的写法导致的 ,不过我们倒是非常想知道如何来规避这个问题,那么接下来就是解答这个问题的关键。
    我们从上面的异常堆栈信息中,可以看出是在BitmapFactory.nativeDecodeAsset(),对应该方法的native方法是在BitmapFactory.cpp中的doDecode()方法,在该方法中申请JavaPixelAllocator对象时,会调用到Graphics.cpp中的setJavaPixelRef()方法,在setJavaPixelRef()中会对解码需要申请的内存空间进行一个判断,代码如下:

    bool r = env->CallBooleanMethod(gVMRuntime_singleton,
    gVMRuntime_trackExternalAllocationMethodID,jsize);

    而JNI方法ID — gVMRuntime_trackExternalAllocationMethodID对应的方法实际上是dalvik_system_VMRuntime.c中的Dalvik_dalvik_system_VMRuntime_trackExternalAllocation(),而在该方法中又会调用大HeapSource.c中的dvmTrackExternalAllocation()方法,继而调用到externalAllocPossible()方法,在该方法中这句代码是最关键的

    heap = hs2heap(hs);
    
    currentHeapSize = mspace_max_allowed_footprint(heap->msp);
    if (currentHeapSize + hs->externalBytesAllocated + n <=
    heap->absoluteMaxSize)
    {
    return true;
    }

    这段代码的意思应该就是当前堆已使用的大小(由currentHeapSize和hs->externalBytesAllocated构成)加上我们需要再次分配的内存大小不能超过堆的最大内存值。那么一个堆的最大内存值究竟是多大呢。通过下面这张图,我们也许可以看到一些线索(自己画的,比较粗糙)

    最终的决定权其实是在Init.c中,因为Android在启动系统的时候会去优先执行这个里面的函数,通过调用dvmStartup()方法来初始化虚拟机,最终调用到会调用到HeapSource.c中的dvmHeapSourceStartup()方法,而在Init.c中有这么两句代码:

    gDvm.heapSizeStart = 2 * 1024 * 1024; // Spec says 16MB; too big for us.
    
    gDvm.heapSizeMax = 16 * 1024 * 1024; // Spec says 75% physical mem

    在另外一个地方也有类似的代码,那就是AndroidRuntime.cpp中的startVM()方法中:

    [indent]strcpy(heapsizeOptsBuf, "-Xmx");
    
    property_get("dalvik.vm.heapsize", heapsizeOptsBuf+4, "16m");
    //LOGI("Heap size: %s", heapsizeOptsBuf);
    opt.optionString = heapsizeOptsBuf;
    [/indent]

    同样也是默认值为16M,虽然目前我看到了两个可以启动VM的方法,具体Android何时会调用这两个初始化VM的方法,还不是很清楚。不过可以肯定的一点就是,如果启动DVM时未指定参数,那么其初始化堆最大大小应该就是16M,那么我们在网上查到了诸多关于解码图像超过8M就会出错的论断是如何得出来的呢?
    我们来看看HeapSource.c中的这个方法的注释

    /*
    
    * External allocation tracking
    *
    * In some situations, memory outside of the heap is tied to the
    * lifetime of objects in the heap. Since that memory is kept alive
    * by heap objects, it should provide memory pressure that can influence
    * GCs.
    */ 
    static bool
    
    externalAllocPossible(const HeapSource *hs, size_t n)
    {
    const Heap *heap;
    size_t currentHeapSize; 
    /* Make sure that this allocation is even possible.
    * Don’t let the external size plus the actual heap size
    * go over the absolute max. This essentially treats
    * external allocations as part of the active heap.
    *
    * Note that this will fail "mysteriously" if there’s
    * a small softLimit but a large heap footprint.
    */
    heap = hs2heap(hs);[/color]
    currentHeapSize = mspace_max_allowed_footprint(heap->msp);
    if (currentHeapSize + hs->externalBytesAllocated + n <=
    heap->absoluteMaxSize)
    {
    return true;
    }
    HSTRACE("externalAllocPossible(): "
    "footprint %zu + extAlloc %zu + n %zu >= max %zu (space for %zu)\n",
    currentHeapSize, hs->externalBytesAllocated, n,
    heap->absoluteMaxSize,
    heap->absoluteMaxSize -
    (currentHeapSize + hs->externalBytesAllocated));
    return false;
    }

    标为红色的注释的意思应该是说,为了确保我们外部分配内存成功,我们应该保证当前已分配的内存加上当前需要分配的内存值,大小不能超过当前堆的最大内存值,而且内存管理上将外部内存完全当成了当前堆的一部分。也许我们可以这样理解,Bitmap对象通过栈上的引用来指向堆上的Bitmap对象,而Bitmap对象又对应了一个使用了外部存储的native图像,实际上使用的是byte[]来存储的内存空间,如下图:
    <IGNORE_JS_OP>

    原文:http://www.eoeandroid.com/thread-203672-1-1.html

  • 相关阅读:
    java.logging的重定向?
    java.rmi.NoSuchObjectException: no such object in table
    jmx : ClientCommunicatorAdmin Checker-run
    jmx完整示例
    Android studio 下的SDK Manager只显示已安装包的情况
    Android Studio: Error:Cannot locate factory for objects of type DefaultGradleConnector, as ConnectorServiceRegistry
    浅谈Kotlin(二):基本类型、基本语法、代码风格
    浅谈Kotlin(一):简介及Android Studio中配置
    源码浅谈(一):java中的 toString()方法
    ButterKnife注解框架详解
  • 原文地址:https://www.cnblogs.com/vus520/p/2708219.html
Copyright © 2020-2023  润新知