2010-06-14 7 views
34

Ho un'eccezione OutOfMemory con una galleria su 600x800 pixel JPEG.Android: OutofMemoryError: la dimensione della bitmap supera il budget della macchina virtuale senza motivo Posso vedere


L'ambiente

Sto usando Galleria con immagini JPG intorno 600x800 pixel.

Poiché il mio contenuto potrebbe essere un po 'più complesso delle semplici immagini, ho impostato ogni vista come un RelativeLayout che avvolge ImageView con il JPG.

Al fine di "accelerare" l'esperienza utente, ho una cache semplice di 4 slot che prefetta (in un looper) circa 1 immagine a sinistra e 1 immagine a destra sull'immagine visualizzata e le mantiene in una slot HashMap a 4 slot.

La piattaforma

Sto usando AVD di 256 MB di RAM e 128 Dimensione heap, con uno schermo 600x800. Si verifica anche su un target di Entourage Edge, tranne per il fatto che con il dispositivo è più difficile eseguire il debug.


Il problema

Sono stato sempre un'eccezione:

OutofMemoryError: bitmap size exceeds VM budget 

E succede quando va a prendere la quinta immagine. Ho provato a cambiare la dimensione della mia cache di immagini, ed è sempre la stessa.


La cosa strana: Non ci dovrebbe essere un problema di memoria

Al fine di assicurarsi che il limite di heap è molto lontano da quello che mi serve, ho definito un array di 8MB manichino nella all'inizio, e lasciato senza riferimento, quindi viene immediatamente inviato. È un membro del filo attività ed è definito come segue

static { @SuppressWarnings("unused") 
byte dummy[] = new byte[ 8*1024*1024 ]; }  

Il risultato è che la dimensione heap è quasi 11MB ed è tutto gratuito. Nota Ho aggiunto quel trucco dopo che ha iniziato a bloccarsi. Rende OutOfMemory meno frequente.

Ora sto utilizzando DDMS. Poco prima dello schianto (non cambia molto dopo l'incidente), DDMS mostra:

ID Heap Size Allocated Free  %Used #Objects 
1 11.195 MB 2.428 MB 8.767 MB 21.69% 47,156 

E nella tabella di dettaglio mostra:

Type Count Total Size Smallest Largest Median Average 
free 1,536 8.739MB  16B  7.750MB 24B  5.825KB 

Il blocco più grande è 7.7MB. Eppure il LogCat dice:

ERROR/dalvikvm-heap(1923): 925200-byte external allocation too large for this process. 

Se vi occupate la relazione tra la mediana e la media, è plausibile supporre che la maggior parte dei blocchi disponibili sono molto piccole. Tuttavia, c'è un blocco abbastanza grande per la bitmap, è 7.7M. Come mai non è ancora abbastanza?

Nota: ho registrato una traccia heap. Osservando la quantità di dati allocati, non sembra che siano allocati più di 2M. Corrisponde al rapporto di memoria gratuito di DDMS.


  • Potrebbe essere che provo qualche problema come heap-frammentazione?
  • Come posso risolvere/risolvere il problema?
  • L'heap è condiviso con tutti i thread?
  • Potrebbe essere che io interpreti la lettura DDMS in modo errato e non ci sia davvero nessun blocco 900K da allocare? Se è così, qualcuno può dirmi dove posso vederlo?

Grazie mille

Meymann

+0

È possibile ottenere alcuni morsi qui se si pubblica il codice che in realtà preleva/decodifica/memorizza nella cache/in scadenza le bitmap. Il problema è quasi certamente quello che stai facendo lì, non qualcosa che richiede di scavare nell'interno dell'allocazione dell'heap. –

+0

Durante il debug del codice, il mio principale sospetto è sempre il mio codice. Per eseguire il debug, ottengo suggerimenti dall'ambiente. Sfortunatamente, in questo caso: A. la creazione di un codice semplice che legge 3 immagini di 600x800 alla volta in una cache produrrebbe risultati simili sporadicamente (verificati, per fare in modo che accada più rapidamente, si possono aggiungere matrici non referenziate), B. Uso gli strumenti per sondare il problema, ma i suggerimenti che ottengo dagli strumenti non coincidono. C. Il punto di questa domanda è ottenere suggerimenti su ciò che è buona pratica, cosa c'è di sbagliato con le mie conclusioni sulla lettura DDMS, e se c'è una soluzione alternativa. – Meymann

+1

** Un modo molto semplice per riprodurre il problema in un altro modulo ** 1. nella classe Attività, aggiungere static {byte dummy [] = new byte [4096]; } per forzare l'espansione dell'heap (e rimuovere i dubbi). 2. creare un ViewFlipper. 3. Aggiungere circa 10 ImageView in cui ciascuno si riferisce a un drawable bitmap 600x800. 4. Quando si blocca, guarda il DDMS. – Meymann

risposta

12

Penso che nel tuo caso non ci sia niente di speciale. Non c'è abbastanza memoria. Non è possibile avere più bitmap 600x800 in memoria, consumano troppa memoria. Dovresti salvarli su SD e caricarli su richiesta. Penso che sia esattamente quello che fai.

Una cosa da tenere presente: DDMS visualizza il consumo della memoria heap java. Ma c'è anche la memoria nativa che non è visualizzata in DDMS. E i bitmap per quanto ho capito sono creati nella memoria nativa. Quindi DDMS è solo un cattivo strumento per tenere traccia di questi problemi di memoria. Devi solo essere sicuro di liberare la memoria, che le immagini vengono raccolte da Garbage Collector dopo che non ne hai più bisogno.

Garbage Collector funziona secondo il proprio programma. Ecco perché dovresti chiamare il metodo Bitmap.recycle() su bitmap di cui non hai più bisogno. Questo metodo libera esattamente la memoria nativa che stai esaurendo. In questo modo non dipendi da GC e puoi liberare il più grande pezzo di memoria il prima possibile.

Prima di tutto è necessario assicurarsi di non perdere i bitmap.

Ecco un bel post sulle allocazioni di memoria, che può aiutare a scavare più a fondo

+0

Certo, il caricamento lento da SD è quello che ho fatto. Questo è ciò che Gallery sta facendo. Sto usando una cache da 4 slot per accelerarla, comunque. Tuttavia, ci sono 3 cose che devo capire: 1. Come faccio a forzare Gallery per inviare le immagini inutilizzate al più presto? 2. Come mai non è abbastanza? 800x600x4imagesx3bytesPerPixel = 5M che è più piccolo di 16M. 3. Mi chiedo perché è così sporadico. A volte funziona e vola sempre in posti diversi. Grazie – Meymann

+3

Mentre si utilizza la cache a 4 slot, è possibile chiamare recycle() quando si rimuove la vecchia immagine da questa cache. – Fedor

+1

Quando viene chiamato getView nell'adattatore, convertView viene passato a voi. In realtà è una visione che viene riciclata. L'istanza bitmap che viene visualizzata su questa vista non è più necessaria. Quindi immagino che puoi visualizzare bitmap su quel ImageView e riciclarlo. È qualcosa di simile all'approccio ASAP. – Fedor

5

Non so se si tratta di un'opzione per voi, ma avete provato supercampionamento immagini Strange out of memory issue while loading an image to a Bitmap object?

+1

Grazie, ma temo che non lo faccia ... Elimina la qualità dell'immagine, poiché consente al decoder di essere più "sciatto". Di conseguenza, il testo e piccoli frammenti dell'immagine diventano molto imbrattati ... Come il JPEG sa come macchiare ... In realtà assomiglia a quello che succede quando si prende la miniatura JPEG EXIF ​​e la si ridimensiona a fondo scala. Grazie – Meymann

0

ho affrontato anche simile paio questione di settimane fa e ho risolto scalando verso il basso le immagini fino a punto ottimale. Ho scritto un approccio completo nel mio blog here e ho caricato il progetto di esempio completo con il codice inclinato di OOM e il codice di prova OOM here.

0

C'è stato molto tempo da quando l'ho chiesto.

La risposta deve essere divisa in 2 parti: Pre-Gingerbread: si utilizzano solo immagini di piccole dimensioni, utilizzare un sottocampionamento, forse una foto di dimensioni dello schermo e sperare per il bene. Cerca di assicurarti di non assegnare piccoli oggetti che non puoi liberare prima di ottenere una bitmap. Pre-Ginger, la memoria per bmps doveva essere contigua e non era conteggiata nella memoria della VM. Guarda sempre il logcat. Guarda una lezione sulla memoria da Google IO 2011. Post Ginger è più facile. Dal momento che Honeycomb, le immagini bitmap vengono contate anche nella tua area java. Non c'è area jni. Usa sempre il riciclo per le bitmap che non ti servono. Non aspettare il GC.

0

La domanda è stata posta nel 2010, quando Froyo era fresco. Così tante cose sono successe da allora. Prima di 3.0, le bitmap venivano allocate in JNI. La memoria non è stata mostrata nelle statistiche di Dalvik. Non deve più essere monolitico. Prima della 2.3, le statistiche della memoria per JNI non erano disponibili (decodifica bitmap chiamate JNI) in logcat. 4.4 evacuato più spazio. 5.0 il big bang di Art. Nel 2010, Nexus One era di fascia alta, con meno di 300 MB. Il budget per un'app era di circa 16 MB. Ora giorni, c'è circa 8 volte quella memoria.