Android Bitmap Limite - la Prévention de java.lang.OutOfMemory

Je suis actuellement aux prises avec une étrange comportement de la plate-forme Android -- le Bitmap /Java heap limite de mémoire. Selon l'appareil, Android limite le développeur de l'application à 16, 24 ou 32 mo de Java heap space (ou vous pouvez trouver toute valeur aléatoire sur un téléphone enracinée). C'est sans doute assez faible, mais relativement simple que je peux mesurer l'utilisation avec la suite de l'API:

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

Assez facile, maintenant, pour le twist. Dans Android, bitmaps, à quelques exceptions près, sont stockés dans le tas natif et ne comptent pas dans le tas Java. Certains aux yeux brillants, puriste développeur chez Google a décidé que ce n'était "mauvais" et a permis au développeur d'obtenir "plus que leur juste part." Donc, il ya ce joli petit morceau de code qui calcule le natif de l'utilisation de la mémoire encourus par les bitmaps, et éventuellement d'autres ressources, et les sommes que le Java heap et si vous y aller ..... java.lang.OutOfMemory. ouch

Mais pas une grosse affaire. J'ai beaucoup d'images, et n'a pas besoin d'eux tout le temps. Je peux "page" certains de ceux qui ne sont pas utilisés pour le moment:

Donc, pour tentative #1, j'ai refait le code afin que je puisse envelopper chaque bitmap charge avec un 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
    }
}

Voir, voici un petit extrait de journal montrant mon code de capture de l'exception, et la réutilisation de certaines images bitmap:

E/Epic (23221): OUT_OF_MEMORY (pris de java.lang.Dépassement de mémoire) 
J'/Epic (23221): ArchPlatform[android].logStats() - 
J'/Epic (23221): LoadedClassCount=0.00 M 
J'/Epic (23221): GlobalAllocSize=0.00 M 
J'/Epic (23221): GlobalFreedSize=0,02 M 
J'/Epic (23221): GlobalExternalAllocSize=0.00 M 
J'/Epic (23221): GlobalExternalFreedSize=0.00 M 
J'/Epic (23221): EpicPixels=26.6 M (ce qui est 4 * #pixels dans tous les chargement des images) 
J'/Epic (23221): NativeHeapSize=29.4 M 
J'/Epic (23221): NativeHeapAllocSize=25.2 M 
J'/Epic (23221): ThreadAllocSize=0.00 M 
J'/Epic (23221): totalMemory()=9,1 M 
J'/Epic (23221): maxMemory()=32.0 M 
J'/Epic (23221): freeMemory()=4,4 M 
W/Epic (23221): Recyclage bitmap 'game_word_puzzle_11_aniframe_005' 
J'/Epic (23221): BITMAP_RECYCLING recyclé: 1 bitmaps pour une valeur de 1,1 M). âge=294 

Notez comment la mémoire - freeMemory est que de 4,7 MiB, mais avec ~26? MiB de mémoire natif prises par les bitmaps, nous sommes dans le 31/32 MiB plage où nous avons atteint la limite. Je suis encore un peu confus ici mon compte-rendu de tous les chargement des images est de 26,6 MiB, pourtant, le natif alloc taille est seulement de 25,2 MiB. Donc je compte quelque chose de mal. Mais il est tout de rester dans la course et certainement démontre la croix-piscine "sommation" qui se passe avec les mem-limite.

Je PENSAIS que je l'avais résolu. Mais non, Android ne serait pas renoncer si facilement...

Voici ce que je reçois de deux de mes quatre périphériques de test:

I/dalvikvm-tas(17641): Pince cible GC tas de 32.687 MO à 32.000 MO 
D/dalvikvm(17641): GC_FOR_MALLOC libérés <1K, 41% gratuit 4684K/7815K, externe 24443K/24443K, pause 24ms 
D/dalvikvm(17641): GC_EXTERNAL_ALLOC libérés <1K, 41% gratuit 4684K/7815K, externe 24443K/24443K, pause 29ms 
E/dalvikvm-tas(17641): 1111200 octets externe allocation trop grand pour ce processus. 
E/dalvikvm(17641): de mémoire: Taille de Segment=7815KB, Alloués=4684KB, Taille du Bitmap=24443KB, Limit=32768KB 
E/dalvikvm(17641): Garniture info: Empreinte=7815KB, a Permis Empreinte=7815KB, Parés=880KB 
E/GraphicsJNI(17641): VM ne nous laisse pas allouer 1111200 octets 
J'/dalvikvm-tas(17641): Pince cible GC tas de 32.686 MO à 32.000 MO 
D/dalvikvm(17641): GC_FOR_MALLOC libérés <1K, 41% gratuit 4684K/7815K, externe 24443K/24443K, pause 17ms 
J'/DEBUG ( 1505): *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** 
J'/DEBUG ( 1505): Construire des empreintes digitales: 'verizon_wwe/htc_mecha/mecha:2.3.4/GRJ22/98797:utilisateur/version-clés" 
J'/DEBUG ( 1505): pid: 17641, tid: 17641 
J'/DEBUG ( 1505): signal 11 (SIGSEGV), le code 1 (SEGV_MAPERR), faute d'adresse 00000000 
J'/DEBUG ( 1505): r0 0055dab8 r1 00000000 r2 00000000 r3 0055dadc 
J'/DEBUG ( 1505): r4 0055dab8 r5 00000000 r6 00000000 r7 00000000 
J'/DEBUG ( 1505): r8 000002b7 r9 00000000 00000000 10 fp 00000384 
J'/DEBUG ( 1505): ip 0055dab8 sp befdb0c0 lr 00000000 pc ab14f11c cpsr 60000010 
J'/DEBUG ( 1505): d0 414000003f800000 d1 2073646565637834 
J'/DEBUG ( 1505): d2 4de4b8bc426fb934 d3 42c80000007a1f34 
J'/DEBUG ( 1505): d4 00000008004930e0 d5 0000000000000000 
J'/DEBUG ( 1505): d6 0000000000000000 d7 4080000080000000 
J'/DEBUG ( 1505): d8 0000025843e7c000 d9 c0c0000040c00000 
J'/DEBUG ( 1505): d10 40c0000040c00000 d11 0000000000000000 
J'/DEBUG ( 1505): d12 0000000000000000 d13 0000000000000000 
J'/DEBUG ( 1505): d14 0000000000000000 0000000000000000 d15 
J'/DEBUG ( 1505): d16 afd4242840704ab8 d17 0000000000000000 
J'/DEBUG ( 1505): d18 0000000000000000 0000000000000000 d19 
J'/DEBUG ( 1505): d20 0000000000000000 d21 0000000000000000 
J'/DEBUG ( 1505): d22 0000000000000000 0000000000000000 d23 
J'/DEBUG ( 1505): d24 0000000000000000 0000000000000000 d25 
J'/DEBUG ( 1505): d26 0000000000000000 0000000000000000 d27 
J'/DEBUG ( 1505): d28 00ff00ff00ff00ff d29 00ff00ff00ff00ff 
J'/DEBUG ( 1505): d30 0000000000000000 d31 3fe55167807de022 
J'/DEBUG ( 1505): scr 68000012 

C'est un natif de crash. Une erreur de segmentation non moins (sig11). Par définition, une erreur est TOUJOURS un bug. C'est absolument un Android bug dans le code natif de la manipulation de GC et/ou mem-de limiter la vérification. Mais c'est toujours mon appli qui plante résultant dans de mauvaises critiques, les retours, et la baisse des ventes.

Donc je dois calculer la limite de moi-même. Sauf que j'ai eu du mal ici. J'ai essayé d'ajouter les pixels de moi-même (EpicPixels), mais j'ai toujours frappé le memcrash périodiquement, je suis donc sous-dénombrement quelque chose. J'ai essayé d'ajouter le javaBytes (total gratuit) à NativeHeapAllocSize, mais ce serait de temps en temps la cause de mon app pour devenir "anorexique", de libérer et de libérer des images bitmap jusqu'à ce qu'il n'y avait plus rien à purger.

  1. Personne ne sait le exacte de calcul utilisée pour calculer la limite de la mémoire et de déclencher java.lang.OutOfMemory?

  2. quelqu'un d'autre A touché à cette question et a travaillé à travers elle? Avez-vous des perles de la sagesse?


  3. personne Ne sait qui employé de Google a imaginé ce stratagème afin que je puisse lui donne un coup de la ruine de ~40 heures de ma vie? j/k

RÉPONSE: La limite est de NativeHeapAllocSize < maxMemory(); toutefois, en raison de la fragmentation de la mémoire, Android se bloque bien avant la limite réelle. Ainsi, vous devez vous limiter à une valeur un peu inférieure à la limite. Ce "facteur de sécurité" est d'application dépendante, mais un peu de MiB semble fonctionner pour la plupart des gens. (je peux juste dire que je suis époustouflé par la façon dont brisé ce comportement est)

InformationsquelleAutor Dave Dopson | 2011-07-31