2016-07-10 64 views
10

La mappatura della memoria di un file di grandi dimensioni su Android in Java funziona correttamente. Ma quando la mappatura più di ~ 1,5 GB in totale anche con la mappatura più chiamate non riesce con:Java mmap non riesce su Android con "mmap non riuscito: ENOMEM (Memoria insufficiente)"

mmap failed: ENOMEM (Out of memory) 

Vedi la discussione completa here. Nota: non fallisce su un server Linux. Android: largeHeap = "true" è abilitato per l'applicazione.

Il seguente codice Java è chiamato a poche centinaia di volte la richiesta ~ 1MB per chiamata:

ByteBuffer buf = raFile.getChannel().map(allowWrites ? FileChannel.MapMode.READ_WRITE : FileChannel.MapMode.READ_ONLY, offset, byteCount); 

da evitare chiedendo un grande pezzo memoria contigua che è spesso più difficile da trovare. Vedi il codice completo here. Tieni presente che il raddoppio della "dimensione del segmento" (ovvero la dimensione di una singola chiamata di mappa) non ha alcun effetto, il che significa che si ferma nella posizione di memoria simile. Inoltre è importante notare che 2 app con entrambi leggermente sotto il limite stanno eseguendo bene (suggerendo un limite per processo).

Domande correlate sono here, here, here, here, here, here, here e here.

L'utilizzo di più file invece di un file con più associazioni è utile?

Ho letto che questo potrebbe essere un limite per processo per lo spazio indirizzo virtuale. Dove posso trovare di più su questo? Posso modificare questa impostazione con NDK, ad es. come chiamare ulimit? Potrebbe madvise aiutarmi un po 'qui?

Aggiornamento

Vedere la mia risposta here per uno strumento di mmap utilizzabile in Java

+0

L'unica soluzione è probabilmente quella di implementare una cache upstream e chiamare 'mapIt' su richiesta. Ma il problema è come rimuovere un mapping di quelle parti in quanto è hackerato con il desktop java ('mmap cleaner') e non è possibile con Android? – Karussell

+0

Chiesto questo qui: http://stackoverflow.com/questions/38315292/unmapping-or-release-a-mappedbytebuffer-under-android – Karussell

risposta

6

Il tuo problema è sicuramente causato da virtuale spazio di indirizzamento estenuante. Probabilmente il tuo problema si riproduce su dispositivi Android a 32 bit, dove lo spazio per gli indirizzi degli utenti è fisicamente limitato a 2 GB e non può essere urtato. (Anche se potrebbe essere 3 GB (improbabile) ed è configurato durante il processo di build del SO). Probabilmente ~ 500 MB è usato per le librerie di sistema, JVM e il suo heap. E ~ 1.5 GB sono disponibili per te.

L'unico modo in questa situazione IMO - continua a mappare solo le sezioni di file che sono realmente utilizzate ora e non mappare quelle non utilizzate il prima possibile. Puoi utilizzare una sorta di finestra scorrevole in cui solo una piccola parte del file verrà mappata alla memoria, e al termine - non mappare quella parte, far avanzare la posizione della finestra e mappare quella finestra aggiornata, così via.

Anche quando si mappa un intero file di grandi dimensioni, il processo diventa una vittima attraente per il killer Out of Of Memory del sistema. Perché quando leggi questo file mappato - il consumo di memoria fisica aumenta e in un momento qualsiasi processo verrà ucciso.

+0

Dai report degli utenti viene visualizzato anche su sistemi a 64 bit, ma forse c'è qualche impostazione statica in Android. La tua soluzione suggerita non è banale in Android poiché la non mappatura esplicita non è supportata o conosci uno strumento per questo? Vedi la mia altra domanda http://stackoverflow.com/questions/38315292/unmapping-or-release-a-mappedbytebuffer-under-android Inoltre, perché pensi che aumenterà l'utilizzo della memoria fisica? L'utilizzo della memoria dovrebbe essere piccolo IMO trascurabile soprattutto se si mappa l'intero file – Karussell

+1

È possibile controllare il limite della dimensione dello spazio indirizzo con 'getrlimit (RLIMIT_AS)' (solo per codice nativo, sembra che java non abbia modo di eseguire tale attività). Successivamente, sull'utilizzo della memoria fisica. Quando si mappano i file in memoria, in generale ci vorrà tutta la memoria fisica che si è mappata. Ma potresti non vederlo immediatamente perché la mappatura può essere creata in modo non bloccante, senza read-ahead e completa allocazione della memoria. I dati verranno mappati su nuove pagine su richiesta. È fatto dal kernel ed è trasparente per l'utente. Prova a leggere in sequenza dall'associazione byte per byte - vedrai aumentare l'utilizzo della memoria. – Sergio

+0

Sì, sembra impossibile la decompressione implicita tramite l'API Java. Quindi potrebbe essere ragionevole implementare il proprio modulo nativo di mapping/unmapping che userà 'mmap()'/'munmap()' e fornirà i dati mappati tramite [buffer di byte diretti] (https://docs.oracle.com/javase /8/docs/technotes/guides/jni/jni-14.html#NewDirectByteBuffer). Dovrebbe essere un approccio efficace. E ti dà più flessibilità in ciò che vuoi mappare e quando. – Sergio

0

prova ad aggiungere largeHeap nel tuo manifest. Può essere che funzioni

+0

Non funziona (già fatto). Anche heap non ha molto da fare in questo caso con la memoria virtuale. – Karussell

1

Poiché non è possibile aumentare il limite dell'indirizzo virtuale su Android tramite un'API (che non richiede l'accesso di root), inoltre non l'ho ancora visto nel codice sorgente di Android. L'unica soluzione possibile che vedo è quella di implementare un tipo di cache, che suddivide i segmenti su access e rilascia vecchi segmenti se un certo numero di segmenti è già stampato.Questo significa che stiamo facendo il lavoro che il sistema operativo normalmente fa per noi automaticamente, il che è un po 'brutto.

Per farlo funzionare con Android si può usare this answer/util-mmap. Speriamo che qualcuno possa implementare una cache MMAP simile ad Android ad un certo punto, forse anche a noi :)