2016-07-10 13 views
10

Speicherzuordnung eine große Datei auf Android in Java funktioniert gut. Aber wenn insgesamt mehr als ~ 1,5 GB Kartierung auch bei mehreren Mapping nennt es irgendwie:Java mmap schlägt auf Android mit "mmap fehlgeschlagen: ENOMEM (nicht genügend Arbeitsspeicher)"

mmap failed: ENOMEM (Out of memory) 

Die gesamte Diskussion here. Hinweis: Es scheitert nicht auf einem Server Linux. Der Android: largeHeap = "true" ist für die Anwendung aktiviert.

Der folgende Java-Code wird ein paar hundert Mal aufgerufen ~ 1MB pro Anruf anfordernden:

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

zu vermeiden, einen großen zusammenhängenden Speicher chunk anfordert, die gefunden zu werden oft schwieriger ist. Siehe den vollständigen Code here. Denken Sie daran, dass eine Verdopplung der "Segmentgröße" (d. H. Die Größe eines einzelnen Kartenaufrufs) keine Wirkung hat, was bedeutet, dass sie an der gleichen Speicherposition anhält. Es ist auch wichtig zu beachten, dass 2 Apps mit etwas unter dem Limit gut ausgeführt werden (Hinweis auf ein pro Prozesslimit).

Verwandte Fragen sind here, here, here, here, here, here, here und here.

Wird die Verwendung mehrerer Dateien statt einer Datei mit mehreren Zuordnungen helfen?

Ich habe gelesen, dass dies pro Prozesslimit sein könnte für den virtuellen Adressraum. Wo kann ich mehr darüber finden? Kann ich diese Einstellung mit NDK ändern, z. wie anzurufen ulimit? Könnte mir madvise hier ein bisschen helfen?

aktualisieren

Siehe meine Antwort here für ein Mmap Werkzeug verwendbar in Java

+0

Die einzige Lösung ist wahrscheinlich, einen Upstream-Cache zu implementieren und 'mapIt' bei Bedarf aufzurufen. Aber das Problem ist, wie man diese Teile entmapselt, da es mit Desktop-Java ('mmap cleaner') hackisch ist und mit Android nicht möglich ist? – Karussell

+0

Fragte man dies hier: http://StackOverflow.com/Questions/38315292/Unmapping-or-Release-AmappedByteBuffer-under-android – Karussell

Antwort

6

Ihr Problem durch virtuellen Adressraum anstrengend sicherlich verursacht wird. Vermutlich reproduziert sich Ihr Problem auf 32-Bit-Android-Geräten, wo der Benutzeradressraum physikalisch auf 2 GB begrenzt ist und nicht gestoßen werden kann. (Obwohl es 3 GB (unwahrscheinlich) sein kann und es während des Betriebssystem-Build-Prozesses konfiguriert wird). Wahrscheinlich werden ~ 500 MB für Systembibliotheken, JVM und seinen Heap verwendet. Und ~ 1,5 GB ist für Sie verfügbar.

Der einzige Weg in dieser Situation IMO - weiterhin nur die tatsächlich verwendeten Dateibereiche zugeordnet werden und unbenutzte so schnell wie möglich auszeichnen. Sie können eine Art von gleitendem Fenster verwenden, in dem nur ein kleiner Teil der Datei dem Speicher zugeordnet wird. Wenn Sie fertig sind, können Sie die Position des Fensters aufheben und das aktualisierte Fenster zuordnen, usw.

Auch wenn Sie eine ganze große Datei abbilden, wird Ihr Prozess ein attraktives Opfer für den Out-of-Memory-Killer des Systems. Weil, wenn Sie solch eine abgebildete Datei lesen - der Verbrauch des physischen Gedächtnisses erhöht und in einem Moment wird der Prozess getötet.

+0

Aus Benutzermeldungen zeigt es sich auch auf 64-Bit-Systemen, aber vielleicht gibt es einige statische Einstellung in Android. Ihre vorgeschlagene Lösung ist unter Android nicht trivial, da explizites Unmapping nicht unterstützt wird, oder kennen Sie ein Tool dafür? Siehe meine andere Frage http://stackoverflow.com/questions/38315292/unmapping-or-release-a-mappedbytebuffer-under-android Außerdem, warum denken Sie, es wird die physische Speichernutzung erhöhen? Die Speicherauslastung sollte vernachlässigbar klein sein IMO, besonders wenn Sie die ganze Datei abbilden – Karussell

+1

Sie können die Adressraum-Größenbeschränkung mit 'getrlimit (RLIMIT_AS)' überprüfen (nur für nativen Code scheint Java keine Möglichkeiten, eine solche Aufgabe zu erfüllen). Als nächstes geht es um die physische Speichernutzung. Wenn Sie eine Datei allgemein in den Speicher ablegen, benötigt sie so viel physischen Speicher wie Sie zugeordnet haben. Sie können es jedoch möglicherweise nicht sofort sehen, da das Mapping in nicht blockierender Weise erstellt werden kann, ohne Read-Ahead und vollständige Speicherzuweisung. Daten werden bei Bedarf neuen Seiten zugeordnet. Es wird vom Kernel gemacht und ist für den Benutzer transparent. Versuchen Sie sequentiell aus dem Mapping Byte für Byte zu lesen - Sie werden sehen, dass die Speichernutzung ansteigt. – Sergio

+0

Ja, scheint wie implizite Unmapping über Java-API ist unmöglich. Dann kann es sinnvoll sein, ein eigenes mapping/unmapping natives Modul zu implementieren, das 'mmap()'/'munmap()' verwendet und zugeordnete Daten über [direkte Bytepuffer] (https://docs.oracle.com/javase) liefert /8/docs/technotes/guides/jni/jni-14.html#NewDirectByteBuffer). Es sollte ein wirksamer Ansatz sein. Und gibt Ihnen mehr Flexibilität bei dem, was Sie zuordnen möchten und wann. – Sergio

0

Versuchen Sie, LargeHeap in Ihrem Manifest hinzuzufügen. Es funktioniert

+0

Funktioniert nicht (schon erledigt). Auch Heap hat in diesem Fall nicht viel mit virtuellem Speicher zu tun. – Karussell

1

Da wir die virtuelle Adressgrenze auf Android über eine API (die keinen Root-Zugriff benötigt) nicht erhöhen kann, habe ich das auch noch nicht im Android-Quellcode gesehen. Die einzige mögliche Lösung, die ich sehe, besteht darin, eine Art Cache zu implementieren, der Segmente beim Zugriff kapselt und ältere Segmente freigibt, wenn eine bestimmte Anzahl von Segmenten bereits gemappt ist.Das heißt, wir machen die Arbeit, die das OS normalerweise automatisch für uns erledigt, was ein bisschen hässlich ist.

Um es unter Android arbeiten zu lassen, kann this answer/util-mmap verwendet werden. Hoffentlich kann jemand einen solchen MMAP-Cache für Android irgendwann implementieren, vielleicht sogar uns :)