Warum erhalte ich „nicht genügend Speicher verfügbar, um diesen Befehl zu verarbeiten“ mit Hilfe von Java MappedByteBuffers?
-
20-09-2019 - |
Frage
Ich habe eine sehr große Auswahl an Doppeln, dass ich eine Disk-basierte Datei verwenden und eine Paging-Liste der MappedByteBuffers zu handhaben, finden Sie unter diese Frage für mehr Hintergrund. Ich bin auf Windows XP mit Hilfe von Java 1.5.
Hier ist der Schlüssel Teil meiner Code, der die Zuordnung der Puffer gegen die Datei wird ...
try
{
// create a random access file and size it so it can hold all our data = the extent x the size of a double
f = new File(_base_filename);
_filename = f.getAbsolutePath();
_ioFile = new RandomAccessFile(f, "rw");
_ioFile.setLength(_extent * BLOCK_SIZE);
_ioChannel = _ioFile.getChannel();
// make enough MappedByteBuffers to handle the whole lot
_pagesize = bytes_extent;
long pages = 1;
long diff = 0;
while (_pagesize > MAX_PAGE_SIZE)
{
_pagesize /= PAGE_DIVISION;
pages *= PAGE_DIVISION;
// make sure we are at double boundaries. We cannot have a double spanning pages
diff = _pagesize % BLOCK_SIZE;
if (diff != 0) _pagesize -= diff;
}
// what is the difference between the total bytes associated with all the pages and the
// total overall bytes? There is a good chance we'll have a few left over because of the
// rounding down that happens when the page size is halved
diff = bytes_extent - (_pagesize * pages);
if (diff > 0)
{
// check whether adding on the remainder to the last page will tip it over the max size
// if not then we just need to allocate the remainder to the final page
if (_pagesize + diff > MAX_PAGE_SIZE)
{
// need one more page
pages++;
}
}
// make the byte buffers and put them on the list
int size = (int) _pagesize ; // safe cast because of the loop which drops maxsize below Integer.MAX_INT
int offset = 0;
for (int page = 0; page < pages; page++)
{
offset = (int) (page * _pagesize );
// the last page should be just big enough to accommodate any left over odd bytes
if ((bytes_extent - offset) < _pagesize )
{
size = (int) (bytes_extent - offset);
}
// map the buffer to the right place
MappedByteBuffer buf = _ioChannel.map(FileChannel.MapMode.READ_WRITE, offset, size);
// stick the buffer on the list
_bufs.add(buf);
}
Controller.g_Logger.info("Created memory map file :" + _filename);
Controller.g_Logger.info("Using " + _bufs.size() + " MappedByteBuffers");
_ioChannel.close();
_ioFile.close();
}
catch (Exception e)
{
Controller.g_Logger.error("Error opening memory map file: " + _base_filename);
Controller.g_Logger.error("Error creating memory map file: " + e.getMessage());
e.printStackTrace();
Clear();
if (_ioChannel != null) _ioChannel.close();
if (_ioFile != null) _ioFile.close();
if (f != null) f.delete();
throw e;
}
ich den Fehler im Titel erwähnt erhalten, nachdem ich den zweiten oder dritten Puffer zuzuweisen.
Ich dachte, es war etwas mit zusammenhängendem Speicher zur Verfügung, dies zu tun versucht hat, es mit verschiedenen Größen und Anzahl der Seiten, aber insgesamt keinen Nutzen.
Was genau funktioniert „nicht genügend Speicher verfügbar, um diesen Befehl zu verarbeiten“ Mittelwert und was, wenn überhaupt, dann kann ich dagegen tun?
Ich dachte, der Punkt des MappedByteBuffers war die Fähigkeit der Lage sein, Strukturen zu behandeln größer als man auf dem Heap passen könnte, und behandeln sie, als ob sie in Erinnerung waren.
Irgendwelche Hinweise?
EDIT:
Als Reaktion auf eine Antwort unten (@adsk) änderte ich meinen Code so dass ich nie zu einem beliebigen Zeitpunkt mehr als ein einziger aktiver MappedByteBuffer habe. Wenn ich beziehe mich auf einen Bereich der Datei, die derzeit nicht zugeordnete I Junk die vorhandene Karte und eine neue erstellen. Ich bekomme immer noch den gleichen Fehler nach etwa 3 Map-Operationen.
Der Bug zitierte mit GC nicht den MappedByteBuffers zu sammeln scheint immer noch ein Problem in JDK 1.5 zu sein.
Lösung
Ich dachte, der Punkt des MappedByteBuffers war die Fähigkeit der Lage sein, Strukturen zu behandeln größer als man auf dem Heap passen könnte, und behandeln sie, als ob sie in Erinnerung waren.
Nein. Die Idee ist / war Sie Adresse, damit mehr als 2 ** 31 Doppelzimmer ... auf der Annahme, dass Sie genügend Gedächtnis hatten, und wurden unter Verwendung eines 64-Bit-JVM.
(Ich gehe davon aus, dass dies eine Followup Frage auf diese Frage .)
Bearbeiten . Offensichtlich mehr Erklärung benötigt wird,
Es gibt eine Reihe von Grenzen, die ins Spiel kommen.
-
Java hat eine fundamentale Einschränkung, dass das
length
Attribut eines Arrays und Array-Indizes Typint
haben. Dies, kombiniert mit der Tatsache, dassint
signiert ist und ein Array kann keine negative Größe Mittel aufweisen, dass die größte mögliche Array2**31
Elemente haben können. Diese Einschränkung gilt für 32-Bit- und 64-Bit-JVMs. Es ist ein grundlegender Bestandteil der Java-Sprache ... wie die Tatsache, dasschar
Werte von0
zu65535
gehen. -
Mit einer 32-Bit-JVM legt eine (theoretische) Ober von
2**32
auf der Anzahl von Bytes gebunden, die von der JVM adressierbar sind. Dazu gehören die gesamte Halde, Code und Bibliotheksklassen, die Sie verwenden, den nativen Code Kern JVM, Speicher für abgebildet Puffer verwendet ... alles. (In der Tat, je nach Plattform, die OS können geben Sie deutlich weniger als2**32
Bytes, wenn Adressraum). -
Die Parameter, die Sie auf der Java-Befehlszeile geben bestimmen, wie viel Heap-Speicher JVM erlauben Ihre Anwendung zu verwenden. Speicher gemappt
MappedByteBuffer
Objekte verwenden, nicht in diese Richtung zählen. -
Die Speichermenge, die das Betriebssystem geben Ihnen hängt (unter Linux / UNIX) auf die Gesamtmenge des Swap-Speicher konfiguriert, der ‚Prozess‘ Grenzen und so weiter. Ähnliche Grenzen wahrscheinlich auf Windows anzuwenden. Und natürlich können Sie nur einen 64-Bit-JVM ausgeführt werden, wenn das Host-Betriebssystem 64-Bit-fähig ist, und Sie sind mit 64-Bit-fähiger Hardware. (Wenn Sie einen Pentium haben, Sie sind einfach kein Glück.)
-
Schließlich kommt die Menge des physischen Speichers in Ihrem System ins Spiel. In der Theorie können Sie Ihre JVM bitten, einen Haufen zu verwenden, etc., die oft größer als als Ihre Maschine physischer Speicher ist. In der Praxis ist dies eine schlechte Idee . Wenn Sie über den virtuellen Speicher zuweisen, wird Ihr System dreschen und Anwendungsleistung wird den Boden gehen.
Die wegzunehmen, ist dies:
-
Wenn Sie eine 32-Bit-JVM verwenden, werden Sie wahrscheinlich irgendwo zwischen
2**31
und2**32
begrenzt Bytes adressierbarer Speicher. Das ist genug Platz für maximal zwischen2**29
und2**30
verdoppelt, ob Sie ein Array oder ein zugeordnetes Buffer verwendet werden. -
Wenn Sie einen 64-Bit-JVM verwenden, können Sie ein einzelnes Array von
2**31
verdoppelt darstellen. Die theoretische Grenze eines zugeordneten Puffers würde2**63
Bytes oder2**61
verdoppelt, aber die praktische Grenze würde in etwa die Menge an physikalischem Speicher des Gerätes hat. sein
Andere Tipps
Wenn der Speicher eine Dateizuordnung ist es möglich, von Adressraum laufen in 32-Bit-VM. Dies geschieht auch, wenn die Datei in kleine Stücke abgebildet wird und diese ByteBuffers sind nicht mehr erreichbar. Der Grund dafür ist, dass GC nie Tritte in die Puffer freizugeben.
Siehe den Bug unter http://bugs.sun.com/bugdatabase /view_bug.do?bug_id=6417205