Android的位图限制 - preventing java.lang.OutOfMemory位图、preventing、Android、OutOfMemory

2023-09-12 11:06:34 作者:将爱意邮寄

我目前受困于Android平台的奇怪的行为 - 位图/ Java堆内存的限制。根据设备的不同,Android的限制了应用程序开发人员为16,24,或MIB爪哇32堆空间(或者你可能会发现一个根深蒂固的手机的任何随机值)。这可以说是相当小,但比较简单,因为我可以测量使用与以下API的:

I'm currently struggling with an odd behavior of the Android platform -- the Bitmap / Java heap memory limit. Depending on the device, Android limits the app developer to 16, 24, or 32 MiB of Java heap space (or you might find any random value on a rooted phone). This is arguably quite small, but relatively straightforward as I can measure usage with the following API's:

Runtime rt = Runtime.getRuntime();
long javaBytes = rt.totalMemory() - rt.freeMemory();
long javaLimit = rt.maxMemory();

很容易;现在的扭曲。在Android中,位图,除了少数例外,都存储在本机堆,并且不向Java堆计数。一些明亮的眼睛,纯粹的开发者在谷歌决定,这是坏,并允许开发者获得超过其公平份额。所以有这个可爱的小片code,计算所发生的位图的本机内存使用情况,和其他可能的资源,并总结了与Java堆,如果你去了..... java.lang.OutOfMemory。 哎哟的

不过,没什么大不了的。我有很多位图,也不需要所有的人所有的时间。我可以页出一些未使用的那一刻的:

But no big deal. I have a lot of bitmaps, and don't need all of them all the time. I can "page out" some of the ones that aren't being used at the moment:

所以,尝试#1,我重构了code,所以我可以包上一个try / catch每一个位图的负载:

So, for attempt #1, I refactored the code so I could wrap every single bitmap load with a try/catch:

while(true) {
    try {
        return BitmapFactory.decodeResource(context.getResources(), android_id, bitmapFactoryOptions);
    } catch (java.lang.OutOfMemory e) {
        // Do some logging

        // Now free some space (the code below is a simplified version of the real thing)
        Bitmap victim = selectVictim();
        victim.recycle();
        System.gc(); // REQUIRED; else, weird behavior ensues
    }
}

你看,这里有一个可爱的小日志片段展示我的code捕捉异常,并回收一些位图:

See, here's a nice little log snippet showing my code catching the exception, and recycling some bitmaps:


E/Epic    (23221): OUT_OF_MEMORY (caught java.lang.OutOfMemory)
I/Epic    (23221): ArchPlatform[android].logStats() -
I/Epic    (23221): LoadedClassCount=0.00M
I/Epic    (23221): GlobalAllocSize=0.00M
I/Epic    (23221): GlobalFreedSize=0.02M
I/Epic    (23221): GlobalExternalAllocSize=0.00M
I/Epic    (23221): GlobalExternalFreedSize=0.00M
I/Epic    (23221): EpicPixels=26.6M (this is 4 * #pixels in all loaded bitmaps)
I/Epic    (23221): NativeHeapSize=29.4M
I/Epic    (23221): NativeHeapAllocSize=25.2M
I/Epic    (23221): ThreadAllocSize=0.00M
I/Epic    (23221): totalMemory()=9.1M
I/Epic    (23221): maxMemory()=32.0M
I/Epic    (23221): freeMemory()=4.4M
W/Epic    (23221): Recycling bitmap 'game_word_puzzle_11_aniframe_005'
I/Epic    (23221): BITMAP_RECYCLING: recycled 1 bitmaps worth 1.1M).  age=294

注意如何totalMemory - freeMemory只有4.7 MIB,但随着〜26?通过位图占用本地内存的MIB,我们是在31/32 MIB范围,其中达到最大极限。我还是有点困惑在这里为所有加载的位图我的标记牌为26.6 MIB,但本地ALLOC尺寸仅为25.2 MIB。所以,我就指望什么问题。但是,这一切都在球场和肯定证明了跨池求和发生的MEM-限制。

我以为我已经是固定的。但是,没有,机器人也不会轻言放弃......

I THOUGHT I had it fixed. But no, Android would not give up so easily...

下面是我从我的两个四个测试设备得到:

Here's what I get from two of my four test devices:


I/dalvikvm-heap(17641): Clamp target GC heap from 32.687MB to 32.000MB
D/dalvikvm(17641): GC_FOR_MALLOC freed <1K, 41% free 4684K/7815K, external 24443K/24443K, paused 24ms
D/dalvikvm(17641): GC_EXTERNAL_ALLOC freed <1K, 41% free 4684K/7815K, external 24443K/24443K, paused 29ms
E/dalvikvm-heap(17641): 1111200-byte external allocation too large for this process.
E/dalvikvm(17641): Out of memory: Heap Size=7815KB, Allocated=4684KB, Bitmap Size=24443KB, Limit=32768KB
E/dalvikvm(17641): Trim info: Footprint=7815KB, Allowed Footprint=7815KB, Trimmed=880KB
E/GraphicsJNI(17641): VM won't let us allocate 1111200 bytes
I/dalvikvm-heap(17641): Clamp target GC heap from 32.686MB to 32.000MB
D/dalvikvm(17641): GC_FOR_MALLOC freed <1K, 41% free 4684K/7815K, external 24443K/24443K, paused 17ms
I/DEBUG   ( 1505): *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
I/DEBUG   ( 1505): Build fingerprint: 'verizon_wwe/htc_mecha/mecha:2.3.4/GRJ22/98797:user/release-keys'
I/DEBUG   ( 1505): pid: 17641, tid: 17641
I/DEBUG   ( 1505): signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 00000000
I/DEBUG   ( 1505):  r0 0055dab8  r1 00000000  r2 00000000  r3 0055dadc
I/DEBUG   ( 1505):  r4 0055dab8  r5 00000000  r6 00000000  r7 00000000
I/DEBUG   ( 1505):  r8 000002b7  r9 00000000  10 00000000  fp 00000384
I/DEBUG   ( 1505):  ip 0055dab8  sp befdb0c0  lr 00000000  pc ab14f11c  cpsr 60000010
I/DEBUG   ( 1505):  d0  414000003f800000  d1  2073646565637834
I/DEBUG   ( 1505):  d2  4de4b8bc426fb934  d3  42c80000007a1f34
I/DEBUG   ( 1505):  d4  00000008004930e0  d5  0000000000000000
I/DEBUG   ( 1505):  d6  0000000000000000  d7  4080000080000000
I/DEBUG   ( 1505):  d8  0000025843e7c000  d9  c0c0000040c00000
I/DEBUG   ( 1505):  d10 40c0000040c00000  d11 0000000000000000
I/DEBUG   ( 1505):  d12 0000000000000000  d13 0000000000000000
I/DEBUG   ( 1505):  d14 0000000000000000  d15 0000000000000000
I/DEBUG   ( 1505):  d16 afd4242840704ab8  d17 0000000000000000
I/DEBUG   ( 1505):  d18 0000000000000000  d19 0000000000000000
I/DEBUG   ( 1505):  d20 0000000000000000  d21 0000000000000000
I/DEBUG   ( 1505):  d22 0000000000000000  d23 0000000000000000
I/DEBUG   ( 1505):  d24 0000000000000000  d25 0000000000000000
I/DEBUG   ( 1505):  d26 0000000000000000  d27 0000000000000000
I/DEBUG   ( 1505):  d28 00ff00ff00ff00ff  d29 00ff00ff00ff00ff
I/DEBUG   ( 1505):  d30 0000000000000000  d31 3fe55167807de022
I/DEBUG   ( 1505):  scr 68000012

这是一个本地崩溃。段错误不低于(sig11)。根据定义,一个段错误永远是一个错误。这绝对是在本机code处理GC和/或MEM-限制检查一个Android的bug。但它仍然是我的应用程序的崩溃导致恶评,退货,和较低的销售。

That's a native crash. A segfault no less (sig11). By definition, a segfault is ALWAYS a bug. This is absolutely an Android bug in the native code handling GC and/or mem-limit checking. But it's still my app that crashes resulting in bad reviews, returns, and lower sales.

所以我要自己计算的极限。 除了我已经在这里战斗过。我尝试添加了自己的像素(EpicPixels),但我还是定期打memcrash,所以我计数不足的东西。我尝试添加的javaBytes(总 - 免费)到NativeHeapAllocSize,但是这样会偶尔导致我的应用程序成为厌食症,解放和释放位图,直到有没有留下来清除

So I have to compute the limit myself. Except that I've struggled here. I tried adding up the pixels myself (EpicPixels), but I still hit the memcrash periodically, so I'm undercounting something. I tried adding the javaBytes (total - free) to NativeHeapAllocSize, but this would occassionally cause my app to become "anorexic", freeing and freeing bitmaps until there was nothing left to purge.

有谁知道的确切的用于计算的内存限制,并引发java.lang.OutOfMemory计算?

Does anyone know the exact computation used to calculate the memory limit and trigger java.lang.OutOfMemory?

有没有其他人遇到这个问题,并通过它的工作?你有没有智慧的任何珍珠?

Has anyone else hit this issue and worked through it? Do you have any pearls of wisdom?

有谁知道谷歌员工想出了这个计划,所以我可以打他毁〜40小时我的生活? J / K

Does anyone know which Google employee dreamed up this scheme so I can punch him for ruining ~40 hrs of my life? j/k

答:限是NativeHeapAllocSize&LT; maxMemory();然而,由于内存碎片,Android的崩溃以及之前的实际限制。因此,你必须限制自己的值比实际限制稍差。这种安全系数是应用程序依赖,但也有少数MIB似乎适用于大多数人。 (可我只想说,我被打破怎么这种行为交口称赞是)

ANSWER: The limit is for NativeHeapAllocSize < maxMemory(); however, due to memory fragmentation, Android crashes well before the actual limit. Thus, you have to limit yourself to a value somewhat less than the actual limit. This "safety factor" is app dependent, but a few MiB seems to work for most people. (can I just say that I'm blown away by how broken this behavior is)

推荐答案

使用此snipplet,为我工作。

Use this snipplet, worked for me

/**
 * Checks if a bitmap with the specified size fits in memory
 * @param bmpwidth Bitmap width
 * @param bmpheight Bitmap height
 * @param bmpdensity Bitmap bpp (use 2 as default)
 * @return true if the bitmap fits in memory false otherwise
 */
public static boolean checkBitmapFitsInMemory(long bmpwidth,long bmpheight, int bmpdensity ){
    long reqsize=bmpwidth*bmpheight*bmpdensity;
    long allocNativeHeap = Debug.getNativeHeapAllocatedSize();


    final long heapPad=(long) Math.max(4*1024*1024,Runtime.getRuntime().maxMemory()*0.1);
    if ((reqsize + allocNativeHeap + heapPad) >= Runtime.getRuntime().maxMemory())
    {
        return false;
    }
    return true;

}

下面是使用的例子

        BitmapFactory.Options bmpFactoryOptions = new BitmapFactory.Options();
        bmpFactoryOptions.inJustDecodeBounds=true;
        BitmapFactory.decodeFile(path,bmpFactoryOptions);
        if ( (runInSafeMemoryMode()) && (!Manager.checkBitmapFitsInMemory(bmpFactoryOptions.outWidth, bmpFactoryOptions.outHeight, 2)) ){
            Log.w(TAG,"Aborting bitmap load for avoiding memory crash");
            return null;        
        }