2011-07-31 3 views
24

저는 안드로이드 플랫폼의 이상한 동작, 즉 비트 맵/자바 힙 메모리 한계로 고심하고 있습니다. 장치에 따라 Android는 앱 개발자를 16, 24 또는 32 MiB의 Java 힙 공간으로 제한합니다 (또는 뿌리를 휴대 전화에서 임의의 값을 찾을 수도 있음). 나는 다음과 같은 API의와 사용을 측정 할 수있는 이것은 틀림없이 매우 작지만 상대적으로 간단하다 : 충분히안드로이드 비트 맵 한도 - java.lang.OutOfMemory 예방

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

쉬운; 지금 비틀어졌다. 안드로이드에서 비트 맵은 거의 예외없이 네이티브 힙에 저장되며 Java 힙에 포함되지 않습니다. 구글의 일부 순수하고 눈에 거슬리는 순수 개발자는 "이것이 나쁘다"고 판단하고 개발자가 "공정한 몫보다 더 많이"얻을 수 있도록 결정했다. 그래서 비트 맵과 다른 리소스에 의해 발생하는 네이티브 메모리 사용량을 계산하는 멋진 코드 조각이 있습니다. 그리고 Java 힙과 합계해서 ..... java.lang.OutOfMemory로 가면됩니다. 우 흐

큰 문제는 없습니다. 나는 많은 비트 맵을 가지고 있으며 항상 모든 것을 필요로하지는 않는다. 나는 지금 사용되지 않는 것들을 "페이지 아웃"할 수 있습니다 :

그래서 try/catch를 사용하여 모든 비트 맵로드를 래핑 할 수 있도록 코드를 리팩토링했습니다 :

~ 26 freeMemory에서 만 4.7 MiB 크기이지만,와 - 어떻게 totalMemory

 
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 

참고 :

         
 
          
  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 } } 
         
 

참조, 여기에 좋은 작은 로그 미리보기가 예외를 잡기 내 코드를 보여, 일부 비트 맵을 재활용입니까? 비트 맵으로 채워진 기본 메모리의 MiB는 우리가 제한에 도달 한 31/32 MiB 범위에 있습니다. 모든로드 된 비트 맵에 대한 실행 기록이 26.6 MiB이지만 기본 할당 크기는 25.2 MiB이므로 여기서 약간 혼란 스럽습니다. 그래서 나는 잘못된 것을 세고있다. 그러나 그것은 모두 야구장에 있으며, mem-limit으로 일어나는 크로스 풀 "합계"를 분명히 보여줍니다.

나는 그것을 고쳐야한다고 생각했다. 하지만, 안드로이드는

여기 내가 네 테스트 장치의 두에서 무엇을 얻을 ... 그렇게 쉽게 포기하지 않을 것이다 : 기본 충돌의

 
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 

합니다. Segfault 이하 (sig11). 정의상 segfault는 항상 버그입니다. 이것은 네이티브 코드 처리 GC 및/또는 mem-limit 검사에서 절대적으로 안드로이드 버그입니다. 하지만 여전히 나쁜 리뷰, 반품 및 낮은 판매로 인한 충돌 애플 리케이션이야.

그래서 제한을 직접 계산해야합니다. 나는 여기에서 고생했다. 픽셀 (EpicPixels)을 직접 추가하려고 시도했지만, 여전히 memcrash가 주기적으로 충돌하여 뭔가를 적게 계산하고 있습니다. 나는 javaBytes (total-free)를 NativeHeapAllocSize에 추가하려고 시도했으나,이 때가되면 내 앱이 "식욕 돋우는 음식"이되고, 아무것도 제거 할 때까지 비트 맵을 해제하고 해제하게됩니다.

  1. 사람이 java.lang.OutOfMemory을 메모리 제한을 계산하고 실행하는 데 사용되는 정확한 계산을 알고 있나요?

  2. 다른 누구도이 문제를 겪었습니까? 당신은 지혜의 진주가 있습니까?

  3. Google 직원 중 누가이 계획을 꿈꾸 었는지 알 수있는 사람이 누구든지 내 인생의 40 시간을 망치기 위해 펀치 할 수 있습니까? J/K

ANSWER : 한계 NativeHeapAllocSize < maxMemory()에 대해이고; 그러나 메모리 조각화로 인해 Android는 실제 제한보다 훨씬 이전에 충돌합니다. 따라서 실제 한계보다 약간 작은 값으로 자신을 제한해야합니다. 이 "안전 요인"은 앱에 따라 다르지만 대부분의 사람들에게는 MiB가 작동하는 것으로 보입니다. (방금이 동작이 어떻게 부러 졌는지 날아가 버렸다고 말할 수 있습니까?)

답변

18

사용이 snipplet을, 나를 위해 일한 다음

/** 
* 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;   
     } 
+0

이것은 사용을 종료 한 코드와 매우 유사합니다. true/false를 반환하는 대신, bitmap.recycle()을 사용하여 공간을 확보합니다 ... –

+1

시도해 보았지만 checkBitmapFitsInMemory()를 사용하면 false를 반환하고 checkBitmapFitsInMemory()를 사용하지 않으면 앱이로드됩니다. 충돌이없는 비트 맵. 내 기록 된 값 : bmpwidth = 93, bmpheight = 131, bmpdensity = 2, reqsize = 24366, heapPad = 4194304, allocNativeHeap = 33150304, maxMemory = 33554432. 비슷한 방법을 쓰는 방법에 대한 아이디어는 있지만 그게 더 효과적일까요? – Geltrude

+2

비트 맵이 더 이상 기본 힙에 저장되지 않으므로이 코드는 Honeycomb 이상에서는 작동하지 않습니다. – vomitcuddle

3

한계는 각 장치에 따라 다릅니다 (비트 맵을로드하려면 3 번째 링크 사용). 여기에 몇 가지 트릭이 있습니다.

  • Application 클래스의 onLowMemory()를 사용하여 충돌을 피하기 위해 메모리를 비우십시오.
  • 비트 맵을 디코딩하기 전에 원하는 크기를 나타냅니다. 추가 정보를 원하시면이 링크를 확인하십시오 :

http://davidjhinson.wordpress.com/2010/05/19/scarce-commodities-google-android-memory-and-bitmaps/

Strange out of memory issue while loading an image to a Bitmap object

이 링크 쇼 힙 BitmapFactory OOM driving me nuts

  • 그리고 물론

    을 확인하기 위해 무료 기존 비트 맵의 ​​메모리를

+2

입니다 Application.onLowMemory(). 불행히도, 그것은 segfault 추락의 제 재판에서 결코 부르지 않습니다. –

2

OK, 기본 모드의 한계가 (총)에서 발생한다고 생각하기 시작했습니다. Java 힙 크기 + 기본 사용 메모리입니다.

한계는 NativeHeapAllocSize 대 maxMemory()를 기준으로합니다. 당신은 내가 22.0 MiB/24 MiB에있는 동안 ~ 1 MiB를 할당하는 것을 중단하고 있다는 것을 아래에서 볼 수 있습니다. 한도는 할당 할 수있는 메모리 양에 대한 상한값입니다. 이것은 잠시 동안 나를 던진 것이다. 한계에 도달하기 전에 크래시가 크게 발생합니다. 따라서 23.999 MiB/24 MiB를 할당하려고 할 때 솔루션의 "memoryPad"값이 필요하므로 거의 100 % 충돌이 발생합니다. 그래서 한계가 24 MiB라면, 얼마나 안전하게 사용할 수 있습니까 ?? 알 수 없는. 20 MiB가 작동하는 것 같습니다. 22 MiB가 작동하는 것 같습니다. 나는 그것보다 더 가깝게 밀고 긴장하고있다. ammount는 malloc 메모리 공간이 네이티브 프로세스에서 조각난 방법에 따라 다릅니다. 그리고 물론,이 중 어느 것도 측정 할 방법이 없으므로 안전 측면에서 오류가 발생합니다.

 
07-31 18:37:19.031: WARN/Epic(3118): MEMORY-USED: 27.3M = 4.2M + 23.0M. jf=1.7M, nhs=23.3M, nhf=0.0M 
07-31 18:37:19.081: INFO/Epic(3118): ArchPlatform[android].logStats() - 
07-31 18:37:19.081: INFO/Epic(3118): LoadedClassCount=0.00M 
07-31 18:37:19.081: INFO/Epic(3118): GlobalAllocSize=0.02M 
07-31 18:37:19.081: INFO/Epic(3118): GlobalFreedSize=0.05M 
07-31 18:37:19.081: INFO/Epic(3118): GlobalExternalAllocSize=0.00M 
07-31 18:37:19.081: INFO/Epic(3118): GlobalExternalFreedSize=0.00M 
07-31 18:37:19.081: INFO/Epic(3118): EpicPixels=17.9M 
07-31 18:37:19.081: INFO/Epic(3118): NativeHeapSize=22.2M 
07-31 18:37:19.081: INFO/Epic(3118): NativeHeapFree=0.07M 
07-31 18:37:19.081: INFO/Epic(3118): NativeHeapAllocSize=22.0M 
07-31 18:37:19.081: INFO/Epic(3118): ThreadAllocSize=0.12M 
07-31 18:37:19.081: INFO/Epic(3118): totalMemory()=5.7M 
07-31 18:37:19.081: INFO/Epic(3118): maxMemory()=24.0M 
07-31 18:37:19.081: INFO/Epic(3118): freeMemory()=1.6M 
07-31 18:37:19.081: INFO/Epic(3118): app.mi.availMem=126.5M 
07-31 18:37:19.081: INFO/Epic(3118): app.mi.threshold=16.0M 
07-31 18:37:19.081: INFO/Epic(3118): app.mi.lowMemory=false 
07-31 18:37:19.081: INFO/Epic(3118): dbg.mi.dalvikPrivateDirty=0.00M 
07-31 18:37:19.081: INFO/Epic(3118): dbg.mi.dalvikPss=0.00M 
07-31 18:37:19.081: INFO/Epic(3118): dbg.mi.dalvikSharedDirty=0.00M 
07-31 18:37:19.081: INFO/Epic(3118): dbg.mi.nativePrivateDirty=0.00M 
07-31 18:37:19.081: INFO/Epic(3118): dbg.mi.nativePss=0.00M 
07-31 18:37:19.081: INFO/Epic(3118): dbg.mi.nativeSharedDirty=0.00M 
07-31 18:37:19.081: INFO/Epic(3118): dbg.mi.otherPrivateDirty=0.02M 
07-31 18:37:19.081: INFO/Epic(3118): dbg.mi.otherPss0.02M 
07-31 18:37:19.081: INFO/Epic(3118): dbg.mi.otherSharedDirty=0.00M 
07-31 18:37:19.081: ERROR/dalvikvm-heap(3118): 1111200-byte external allocation too large for this process. 
07-31 18:37:19.081: ERROR/dalvikvm(3118): Out of memory: Heap Size=6535KB, Allocated=4247KB, Bitmap Size=17767KB 
07-31 18:37:19.081: ERROR/GraphicsJNI(3118): VM won't let us allocate 1111200 bytes 

코드는 모두를 인쇄하기 :


    public static void logMemoryStats() { 
     String text = ""; 
     text += "\nLoadedClassCount="    + toMib(android.os.Debug.getLoadedClassCount()); 
     text += "\nGlobalAllocSize="    + toMib(android.os.Debug.getGlobalAllocSize()); 
     text += "\nGlobalFreedSize="    + toMib(android.os.Debug.getGlobalFreedSize()); 
     text += "\nGlobalExternalAllocSize="  + toMib(android.os.Debug.getGlobalExternalAllocSize()); 
     text += "\nGlobalExternalFreedSize="  + toMib(android.os.Debug.getGlobalExternalFreedSize()); 
     text += "\nEpicPixels="      + toMib(EpicBitmap.getGlobalPixelCount()*4); 
     text += "\nNativeHeapSize="     + toMib(android.os.Debug.getNativeHeapSize()); 
     text += "\nNativeHeapFree="     + toMib(android.os.Debug.getNativeHeapFreeSize()); 
     text += "\nNativeHeapAllocSize="   + toMib(android.os.Debug.getNativeHeapAllocatedSize()); 
     text += "\nThreadAllocSize="    + toMib(android.os.Debug.getThreadAllocSize()); 

     text += "\ntotalMemory()="     + toMib(Runtime.getRuntime().totalMemory()); 
     text += "\nmaxMemory()="     + toMib(Runtime.getRuntime().maxMemory()); 
     text += "\nfreeMemory()="     + toMib(Runtime.getRuntime().freeMemory()); 

     android.app.ActivityManager.MemoryInfo mi1 = new android.app.ActivityManager.MemoryInfo(); 
     ActivityManager am = (ActivityManager)context.getSystemService(Context.ACTIVITY_SERVICE); 
     am.getMemoryInfo(mi1); 
     text += "\napp.mi.availMem="    + toMib(mi1.availMem); 
     text += "\napp.mi.threshold="    + toMib(mi1.threshold); 
     text += "\napp.mi.lowMemory="    + mi1.lowMemory; 

     android.os.Debug.MemoryInfo mi2 = new android.os.Debug.MemoryInfo();   
     Debug.getMemoryInfo(mi2); 
     text += "\ndbg.mi.dalvikPrivateDirty="  + toMib(mi2.dalvikPrivateDirty); 
     text += "\ndbg.mi.dalvikPss="    + toMib(mi2.dalvikPss); 
     text += "\ndbg.mi.dalvikSharedDirty="  + toMib(mi2.dalvikSharedDirty); 
     text += "\ndbg.mi.nativePrivateDirty="  + toMib(mi2.nativePrivateDirty); 
     text += "\ndbg.mi.nativePss="    + toMib(mi2.nativePss); 
     text += "\ndbg.mi.nativeSharedDirty="  + toMib(mi2.nativeSharedDirty); 
     text += "\ndbg.mi.otherPrivateDirty="  + toMib(mi2.otherPrivateDirty); 
     text += "\ndbg.mi.otherPss"     + toMib(mi2.otherPss); 
     text += "\ndbg.mi.otherSharedDirty="  + toMib(mi2.otherSharedDirty); 

     EpicLog.i("ArchPlatform[android].logStats() - " + text); 
    } 
+0

"toMib"이란 무엇입니까? –

+0

@ dpk - toMib는보다 큰 "12.3 MiB"로 큰 바이트 수를 포맷하는 도우미입니다 ...MiB는 base-1000 대신 base-1024 SI 단위를 의미하는 MB의 더 현학적 인 형태입니다. 아마 내가 그것을 사용하고 붙여 넣지 않았다는 것을 몰랐습니다. 코드 의도를 잃지 않고 그것을 제거하거나 원하는 형식으로 만들 수 있습니다. –