2013-06-11 6 views
15

Ich habe den Max-Heap auf 8 GB gesetzt. Wenn mein Programm etwa 6,4 GB verwendet (wie in VisualVM gemeldet), fängt der Garbage Collector an, den größten Teil der CPU zu verbrauchen, und das Programm stürzt mit OutOfMemory ab, wenn eine Zuweisung von ~ 100 MB vorgenommen wird. Ich verwende Oracle Java 1.7.0_21 unter Windows.Warum bekomme ich OutOfMemory, wenn 20% des Heaps noch frei sind?

Meine Frage ist, ob es GC-Optionen gibt, die dabei helfen würden. Ich übergebe nichts außer -Xmx8g.

Meine Vermutung ist der Heap wird fragmentiert, aber sollte der GC nicht komprimieren?

+0

Hat Ihre virtuelle Host-Maschine so viel RAM? Beispiel: Ihre Host-VM (PC) hat 4 GB Arbeitsspeicher, aber Sie geben einer Gast-VM 10 GB Arbeitsspeicher. Ich weiß nicht, ob Paging auftreten würde, aber ich bin nur neugierig auf die Spezifikationen Ihres Host-Systems. –

+0

Meine Maschine verfügt über 10 GB RAM plus 10 GB virtuellen Speicher. –

Antwort

3

Sammeln Stücke von Informationen (die überraschend schwierig ist, da die official documentation ziemlich schlecht ist), habe ich festgestellt habe ...

Generell gibt es zwei Gründe dies passieren kann, im Zusammenhang sowohl zu einer Fragmentierung des freien Raum (dh freier Platz, der in kleinen Teilen existiert, so dass ein großes Objekt nicht zugewiesen werden kann). Erstens führt der Garbage Collector möglicherweise keine Komprimierung durch, was bedeutet, dass der Speicher nicht defragmentiert wird. Selbst ein Sammler, der Kompaktierung macht, mag das nicht ganz gut. Zweitens teilt der Müllsammler typischerweise den Speicherbereich in Regionen auf, die er für verschiedene Arten von Objekten reserviert, und er denkt möglicherweise nicht daran, freien Speicher von der Region zu nehmen, die er der Region geben muss, die ihn benötigt.

Der CMS-Garbage Collector führt keine Komprimierung durch, während die anderen (seriell, parallel, parallelalt und G1) dies tun. Der Standardkollektor in Java 8 ist ParallelOld.

Alle Garbage Collectors teilen Speicher in Regionen, und, AFAIK, alle von ihnen sind zu faul, um sehr schwer zu versuchen, einen OOM-Fehler zu verhindern. Die Befehlszeilenoption -XX:+PrintGCDetails ist für einige Sammler sehr hilfreich, um die Größe der Regionen und den freien Speicherplatz zu zeigen.

Es ist möglich, mit verschiedenen Garbage Collectors und Optimierungsoptionen zu experimentieren. Bezüglich meiner Frage löste der Collector G1 (aktiviert mit dem JVM-Flag -XX:+UseG1GC) das Problem, das ich hatte. Dies war jedoch hauptsächlich auf den Zufall zurückzuführen (in anderen Situationen ist es schneller möglich). Einige der Kollektoren (die Serien, cms und G1) haben umfangreiche Optimierungsoptionen für die Auswahl der Größen der verschiedenen Regionen, damit Sie Zeit damit verschwenden, vergeblich zu versuchen, das Problem zu lösen.

Letztendlich sind die echten Lösungen eher unangenehm. Erstens, installieren Sie mehr RAM. Zweitens, verwenden Sie kleinere Arrays. Drittens ist zu verwenden ByteBuffer.allocateDirect. Direkte Bytepuffer (und ihre int/float/double Wrapper) sind arrayähnliche Objekte mit arrayähnlicher Leistung, die auf dem systemeigenen Heap des Betriebssystems zugewiesen sind. Der Betriebssystem-Heap verwendet die virtuelle Speicherhardware der CPU und ist frei von Fragmentierungsproblemen und kann sogar den Swap-Space der Festplatte effektiv nutzen (wodurch Sie mehr Speicher als verfügbares RAM zuweisen können). Ein großer Nachteil ist jedoch, dass die JVM nicht wirklich weiß, wann direkte Puffer aufgehoben werden sollten, was diese Option für langlebige Objekte wünschenswerter macht. Die letzte, möglicherweise beste und sicherlich unangenehmste Option ist es, zu reservieren und Speicher nativ mit JNI-Aufrufen freizugeben, und es in Java zu verwenden, indem Sie es in eine ByteBuffer umhüllen.

+0

Zum Nachdenken anregende Post. –

0

Wahrscheinlich versuchen Sie, eine große Menge zusammenhängender Speicher zuzuordnen, aber der gesamte freie Speicher ist überall in kleinen Teilen. Wenn der Garbage Collector die gesamte Verarbeitungszeit beansprucht, bedeutet das, dass er gerade versucht, die vielleicht 1 oder 2 Objekte in Ihrem gesamten Objektsatz zu finden, die freigegeben werden können. In diesem Fall denke ich, dass Sie daran arbeiten können, Ihre Objekte zu zerlegen, so dass sie nicht so viel kontinuierlichen Speicher benötigen (zumindest nicht alle gleichzeitig).

Edit: So weit ich weiß, gibt es keine Möglichkeit, dass Java den Speicher packen kann, so dass Sie die vollen 8 GB nutzen können, da der Garbage Collector alle anderen Threads pausieren müsste Sie verschieben ihre Objekte, aktualisieren alle Verweise auf diese Objekte, aktualisieren dann veraltete Cache-Einträge und so weiter ... eine sehr, sehr teure Operation.

diese über Memory Fragmentation

+0

-1 Viele Java (und andere) Speicherbereiniger kompaktieren (wie ich herausgefunden habe). Aber du hast Recht, es ist eine sehr teure Operation, und alle Threads müssen pausiert werden (was als STW oder Stop The World bezeichnet wird). Aber da dies ein nicht interaktives Programm ist, ist das Pausieren kein Problem. –

2

Welche Garbage Collector Siehe verwenden Sie? CMS macht keine Verdichtung. Versuchen Sie es mit der neuen G1 garbage collector - das macht etwas Verdichtung.

Für ein bisschen Kontext: die G1 garbage collector oder `Garbage First‘ Kollektor teilt den Haufen in Stücke und nach (Markierung) alle den Müll identifiziert wird ein chunk evakuieren durch Kopieren aller Live-Bits in ein anderer Brocken - so wird Verdichtung erreicht.

beinhalten die Option nutzen zu können, -XX:+UseG1GC

This eine große Erklärung von G1 und Garbage Collection in Java im Allgemeinen gibt.

+0

Ich benutze die Standardeinstellungen, was auch immer sie sind. Ich werde es versuchen. Scheint wie, was ich brauche. Nur ist diese geringe Verzögerung keine Priorität für mich. Durchsatz ist wichtiger. Sollte ich immer noch G1 verwenden? –

+0

Mithilfe von G1 meldet VisualVM, dass der maximale Heap dem physischen RAM entspricht. Ist das ein Fehler in VisualVM oder kümmert sich G1 nicht um -Xmx? –

+0

Es ist wahrscheinlich CMS standardmäßig verwenden (die keine Komprimierung hat). G1 hat andere nette Eigenschaften, also würde ich das versuchen. Man könnte aber auch den Parallel-Collector (es ist halt die Welt aber parallel) mit dem Befehl '-XX: + UseParallelOldGC' versuchen - das macht Kompaktierung. – selig

2

Immer wenn dieses Problem in der Vergangenheit auftrat, war der tatsächliche freie Speicher viel niedriger als es schien. Sie können die Menge an freiem Speicher drucken, wenn ein OutOfMemoryError auftritt.

+0

Wie erscheint dir "Memory" kostenlos? Die Menge an potentiell verfügbarem Speicher ist eigentlich nicht 'freeMemory()', sondern 'Runtime.getRuntime(). MaxMemory() - Runtime.getRuntime(). TotalMemory + Runtime.getRuntime(). FreeMemory()', wird aber weiter reduziert durch die verschiedenen Faktoren, die ich in meiner Antwort anführe. –

+0

@AleksandrDubinsky freeMemory() ist nicht der freie Speicher, es sei denn, Sie haben Ihre maximale Speichergröße erreicht, die Sie wahrscheinlich auf einem OOME gemacht haben. Dies ist allerdings nicht garantiert. Wenn Sie beispielsweise nach mehr Speicher als Ihrer Heap-Größe fragen, wird möglicherweise kein GC ausgelöst. –

0

-Xmx stellt nur sicher, dass der Heap nicht größer als 8GB ist, gibt aber keine Garantie, dass so viel Speicher tatsächlich zugewiesen wird. Hat Ihr Computer nur 8 GB Arbeitsspeicher?

+0

Meine Maschine hat 10 GB, plus weitere 10 GB im virtuellen Speicher. Der Heap ist bereits 8 GB zugewiesen. –

+1

Das ist einfach zu nah. Der Java-Heap ist nicht dasselbe wie der von Java verbrauchte maximale Speicher, es wird mehr sein. Moderne Maschinen sind "liberal" mit ihrer Verwendung von Speicher, so viel von der Maschine auf den Haufen zu widmen ist wahrscheinlich zu viel. NIEMALS (wie immer) deine JVM tauschen lassen.Du wirst dich selbst, deine Familie, deine Mitarbeiter und ihre Familien hassen. Eine Tausch-JVM ist der Tod. Tu es nicht. Lass es nicht einmal nahe kommen. GC und Swap mischen NICHT. –