Frage

Ich habe ein wirklich seltsames Problem mit einer Java-Anwendung.

Im Wesentlichen handelt es sich um eine Webseite, die Magnolia (ein CMS-System) verwendet. In der Produktionsumgebung sind 4 Instanzen verfügbar.Manchmal geht die CPU in einem Java-Prozess auf 100 %.

Der erste Ansatz bestand also darin, einen Thread-Dump zu erstellen und den betreffenden Thread zu überprüfen. Was ich seltsam fand:

"GC task thread#0 (ParallelGC)" prio=10 tid=0x000000000ce37800 nid=0x7dcb runnable 
"GC task thread#1 (ParallelGC)" prio=10 tid=0x000000000ce39000 nid=0x7dcc runnable 

Ok, das ist ziemlich seltsam, ich hatte noch nie ein solches Problem mit dem Garbage Collector, also haben wir als nächstes JMX aktiviert und mit jvisualvm die Maschine überprüft:Die Heap-Speichernutzung war sehr hoch (95 %).

Naiver Ansatz:Erhöhen Sie den Arbeitsspeicher, sodass das Problem erst nach längerer Zeit auftritt. Dies führt dazu, dass das Problem auf dem neu gestarteten Server mit erhöhtem Arbeitsspeicher (6 GB!) 20 Stunden nach dem Neustart auftrat, während es auf anderen Servern mit weniger Arbeitsspeicher (4 GB!) bereits 10 Stunden nach dem Neustart in Betrieb war Tage dauerte es noch ein paar Tage, bis das Problem wieder auftrat.Außerdem habe ich versucht, das Apache-Zugriffsprotokoll des ausgefallenen Servers zu verwenden und JMeter zu verwenden, um die Anforderungen auf einem lokalen Server wiederzugeben, um den Fehler zu reproduzieren ...es hat auch nicht funktioniert.

Dann habe ich die Protokolle etwas genauer untersucht, um diese Fehler zu finden

info.magnolia.module.data.importer.ImportException: Error while importing with handler [brightcoveplaylist]:GC overhead limit exceeded
at info.magnolia.module.data.importer.ImportHandler.execute(ImportHandler.java:464)
at info.magnolia.module.data.commands.ImportCommand.execute(ImportCommand.java:83)
at info.magnolia.commands.MgnlCommand.executePooledOrSynchronized(MgnlCommand.java:174)
at info.magnolia.commands.MgnlCommand.execute(MgnlCommand.java:161)
at info.magnolia.module.scheduler.CommandJob.execute(CommandJob.java:91)
at org.quartz.core.JobRunShell.run(JobRunShell.java:216)
at org.quartz.simpl.SimpleThreadPool$WorkerThread.run(SimpleThreadPool.java:549)
    Caused by: java.lang.OutOfMemoryError: GC overhead limit exceeded

Ein anderes Beispiel

    Caused by: java.lang.OutOfMemoryError: GC overhead limit exceeded
    at java.util.Arrays.copyOf(Arrays.java:2894)
    at java.lang.AbstractStringBuilder.expandCapacity(AbstractStringBuilder.java:117)
    at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:407)
    at java.lang.StringBuilder.append(StringBuilder.java:136)
    at java.lang.StackTraceElement.toString(StackTraceElement.java:175)
    at java.lang.String.valueOf(String.java:2838)
    at java.lang.StringBuilder.append(StringBuilder.java:132)
    at java.lang.Throwable.printStackTrace(Throwable.java:529)
    at org.apache.log4j.DefaultThrowableRenderer.render(DefaultThrowableRenderer.java:60)
    at org.apache.log4j.spi.ThrowableInformation.getThrowableStrRep(ThrowableInformation.java:87)
    at org.apache.log4j.spi.LoggingEvent.getThrowableStrRep(LoggingEvent.java:413)
    at org.apache.log4j.AsyncAppender.append(AsyncAppender.java:162)
    at org.apache.log4j.AppenderSkeleton.doAppend(AppenderSkeleton.java:251)
    at org.apache.log4j.helpers.AppenderAttachableImpl.appendLoopOnAppenders(AppenderAttachableImpl.java:66)
    at org.apache.log4j.Category.callAppenders(Category.java:206)
    at org.apache.log4j.Category.forcedLog(Category.java:391)
    at org.apache.log4j.Category.log(Category.java:856)
    at org.slf4j.impl.Log4jLoggerAdapter.error(Log4jLoggerAdapter.java:576)
    at info.magnolia.module.templatingkit.functions.STKTemplatingFunctions.getReferencedContent(STKTemplatingFunctions.java:417)
    at info.magnolia.module.templatingkit.templates.components.InternalLinkModel.getLinkNode(InternalLinkModel.java:90)
    at info.magnolia.module.templatingkit.templates.components.InternalLinkModel.getLink(InternalLinkModel.java:66)
    at sun.reflect.GeneratedMethodAccessor174.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:622)
    at freemarker.ext.beans.BeansWrapper.invokeMethod(BeansWrapper.java:866)
    at freemarker.ext.beans.BeanModel.invokeThroughDescriptor(BeanModel.java:277)
    at freemarker.ext.beans.BeanModel.get(BeanModel.java:184)
    at freemarker.core.Dot._getAsTemplateModel(Dot.java:76)
    at freemarker.core.Expression.getAsTemplateModel(Expression.java:89)
    at freemarker.core.BuiltIn$existsBI._getAsTemplateModel(BuiltIn.java:709)
    at freemarker.core.BuiltIn$existsBI.isTrue(BuiltIn.java:720)
    at freemarker.core.OrExpression.isTrue(OrExpression.java:68)

Dann finde ich das heraus Ein solches Problem ist darauf zurückzuführen, dass der Garbage Collector eine Menge CPU verbraucht, aber nicht viel Speicher freigeben kann

Ok, es handelt sich also um ein Problem mit dem SPEICHER, das sich in der CPU manifestiert. Wenn das Problem mit der Speichernutzung gelöst ist, sollte die CPU in Ordnung sein, also habe ich einen Heapdump gemacht, der leider einfach zu groß war, um ihn zu öffnen (der Die Datei war 10 GB groß), trotzdem habe ich den Server lokal ausgeführt, ihn ein wenig geladen und einen Heapdump erstellt. Nachdem ich ihn geöffnet hatte, fand ich etwas Interessantes:

Es gibt eine Menge Fälle davon

AbstractReferenceMap$WeakRef  ==> Takes 21.6% of the memory, 9 million instances
AbstractReferenceMap$ReferenceEntry  ==> Takes 9.6% of the memory, 3 million instances

Außerdem habe ich eine Map gefunden, die anscheinend als „Cache“ verwendet wird (schrecklich, aber wahr). Das Problem besteht darin, dass eine solche Map NICHT synchronisiert ist und von Threads gemeinsam genutzt wird (da sie statisch ist). Das Problem könnte nicht nur darin liegen gleichzeitige Schreibvorgänge, aber auch die Tatsache, dass es bei fehlender Synchronisierung keine Garantie dafür gibt, dass Thread A die von Thread B an der Karte vorgenommenen Änderungen sieht, ich kann jedoch nicht herausfinden, wie ich diese verdächtige Karte mithilfe des Memory-Eclipse-Analysators verknüpfen kann Da die AbstracReferenceMap nicht verwendet wird, handelt es sich lediglich um eine normale HashMap.

Leider verwenden wir diese Klassen nicht direkt (natürlich verwendet der Code sie, aber nicht direkt), sodass ich scheinbar in eine Sackgasse geraten bin.

Probleme gibt es für mich

  1. Ich kann den Fehler nicht reproduzieren
  2. Ich kann nicht herausfinden, wo zum Teufel der Speicher verloren geht (falls das der Fall ist).

Irgendwelche Ideen?

War es hilfreich?

Lösung

Das „No-Op“ finalize() Methoden sollten auf jeden Fall entfernt werden, da sie wahrscheinlich etwaige GC-Leistungsprobleme verschlimmern.Aber ich vermute, dass Sie auch andere Probleme mit Speicherverlusten haben.

Beratung:

  • Beseitigen Sie zunächst das Unnütze finalize() Methoden.

  • Wenn Sie andere haben finalize() Methoden, denken Sie darüber nach, sie loszuwerden.(Abhängig von der Finalisierung ist es im Allgemeinen eine schlechte Idee, Dinge zu tun ...)

  • Versuchen Sie mit dem Speicherprofiler, die Objekte zu identifizieren, die verloren gehen, und herauszufinden, was den Verlust verursacht.Es gibt viele SO-Fragen ...und andere Ressourcen zum Auffinden von Lecks im Java-Code.Zum Beispiel:


Nun zu Ihren speziellen Symptomen.

Zunächst einmal der Ort, an dem die OutOfMemoryErrors geworfen wurden, ist wahrscheinlich irrelevant.

Allerdings ist die Tatsache, dass Sie eine große Anzahl davon haben AbstractReferenceMap$WeakRef Und AbstractReferenceMap$ReferenceEntry Objekte ist eine Zeichenfolge, die darauf hinweist, dass etwas in Ihrer Anwendung oder den von ihr verwendeten Bibliotheken eine große Menge an Caching durchführt ...und dass dieses Caching mit dem Problem verbunden ist.(Der AbstractReferenceMap Die Klasse ist Teil der Apache Commons Collections-Bibliothek.Es ist die Oberklasse von ReferenceMap Und ReferenceIdentityMap.)

Sie müssen das Kartenobjekt (oder die Objekte) aufspüren, das diese sind WeakRef Und ReferenceEntry zu denen Objekte gehören, und die (Ziel-)Objekte, auf die sie verweisen.Dann müssen Sie herausfinden, was sie erstellt und warum die Einträge aufgrund des hohen Speicherbedarfs nicht gelöscht werden.

  • Verfügen Sie über starke Verweise auf die Zielobjekte an anderer Stelle (was verhindern würde, dass die WeakRefs beschädigt werden)?

  • Wird/werden die Karte(n) falsch verwendet, sodass ein Leck entsteht?(Lesen Sie die Javadocs sorgfältig durch ...)

  • Werden die Karten von mehreren Threads ohne externe Synchronisierung verwendet?Dies könnte zu Beschädigungen führen, die sich möglicherweise in einem massiven Speicherleck äußern könnten.


Leider handelt es sich dabei nur um Theorien und es könnten andere Ursachen dafür vorliegen.Und tatsächlich ist es denkbar, dass es sich dabei überhaupt nicht um einen Speicherverlust handelt.


Zum Schluss noch Ihre Beobachtung, dass das Problem umso schlimmer ist, je größer der Heap ist.Für mich stimmt das immer noch mit a überein Reference / Cache-bezogenes Problem.

  • Reference Objekte bedeuten für den GC mehr Arbeit als normale Referenzen.

  • Wenn der GC a „brechen“ muss Reference, das macht mehr Arbeit;z.B.Verarbeitung der Referenzwarteschlangen.

  • Selbst wenn das passiert, können die resultierenden nicht erreichbaren Objekte frühestens im nächsten GC-Zyklus erfasst werden.

Ich kann also sehen, wie ein 6-GB-Heap voller Referenzen den Prozentsatz der im GC verbrachten Zeit erheblich erhöhen würde ...im Vergleich zu einem 4-GB-Heap, und das könnte dazu führen, dass der „GC Overhead Limit“-Mechanismus früher greift.

Aber ich gehe davon aus, dass es sich eher um ein zufälliges Symptom als um die Grundursache handelt.

Andere Tipps

Bei einem schwierigen Debugging-Problem müssen Sie eine Möglichkeit finden, es zu reproduzieren.Nur dann können Sie experimentelle Änderungen testen und feststellen, ob sie das Problem verbessern oder verschlimmern.In diesem Fall würde ich versuchen, Schleifen zu schreiben, die schnell Serververbindungen erstellen und löschen, die eine Serververbindung erstellen und ihr schnell speicherintensive Anfragen senden usw.

Nachdem Sie es reproduzieren können, versuchen Sie, die Heap-Größe zu reduzieren, um zu sehen, ob Sie es schneller reproduzieren können.Aber machen Sie diese Sekunde, da ein kleiner Heap möglicherweise nicht die „GC-Overhead-Grenze“ erreicht, was bedeutet, dass der GC übermäßig viel Zeit (in gewisser Weise 98 %) damit verbringt, Speicher wiederherzustellen.

Bei einem Speicherverlust müssen Sie herausfinden, wo im Code sich Verweise auf Objekte ansammeln.Z.B.Wird eine Karte aller eingehenden Netzwerkanfragen erstellt?Eine Websuche https://www.google.com/search?q=how+to+debug+java+memory+leaks zeigt viele hilfreiche Artikel zum Debuggen von Java-Speicherlecks, einschließlich Tipps zur Verwendung von Tools wie dem Eclipse-Speicheranalysator das du verwendest.Eine Suche nach der spezifischen Fehlermeldung https://www.google.com/search?q=GC+overhead+limit+exceeded ist auch hilfreich.

Das No-Op finalize() Methoden sollten dieses Problem nicht verursachen, aber sie können es durchaus verschlimmern.Das Dokument weiter finalisieren() zeigt, dass mit a finalize() Methode zwingt den GC dazu zweimal Stellen Sie fest, dass die Instanz nicht referenziert ist (vor und nach dem Aufruf). finalize()).

Sobald Sie das Problem reproduzieren können, versuchen Sie, diese No-Op-Fehler zu löschen finalize() Überprüfen Sie die Methoden und prüfen Sie, ob die Reproduktion des Problems länger dauert.

Es ist bezeichnend, dass es viele gibt AbstractReferenceMap$WeakRef Instanzen im Speicher.Der Punkt eines schwache Referenz besteht darin, auf ein Objekt zu verweisen, ohne es zu zwingen, im Gedächtnis zu bleiben. AbstractReferenceMap ist eine Map, die es ermöglicht, die Schlüssel und/oder Werte als schwache Referenzen oder weiche Referenzen festzulegen.(Der Punkt von a weiche Referenz besteht darin, zu versuchen, ein Objekt im Speicher zu behalten, es aber vom GC freigeben zu lassen, wenn der Speicher knapp wird.) Wie auch immer, all diese WeakRef-Instanzen im Speicher verschlimmern wahrscheinlich das Problem, sollten aber die referenzierten Map-Schlüssel/-Werte nicht im Speicher behalten.Worauf beziehen sie sich?Was bezieht sich sonst noch auf diese Objekte?

Versuchen Sie ein Werkzeug, das die Lecks in Ihrem Quellcode lokalisiert, z. B. plumbr

Es gibt eine Reihe von Möglichkeiten, von denen Sie vielleicht einige erforscht haben.

Es ist definitiv ein Speicherleck einer Art.

Wenn Ihr Server Benutzersitzungen hat und Ihre Benutzersitzungen nicht auslaufen oder ordnungsgemäß entsorgt werden, wenn der Benutzer für mehr als x Minuten / Stunden inaktiv ist, erhalten Sie einen Aufbau des verwendeten Speicherplatzes.

Wenn Sie ein oder mehrere Karten von etwas haben, das Ihr Programm erzeugt, und Sie können die Karte der alten / nicht benötigten Einträge nicht löschen, können Sie erneut einen Aufbau des verwendeten Speicherplatzes erhalten. Zum Beispiel habe ich einmal angesehen, dass ich eine Karte hinzufügte, um Prozessgewindungen zu verfolgen, so dass ein Benutzer info von jedem Thread erhalten könnte, bis mein Chef darauf hingewiesen hat, dass keinerlei Gewindefäden auf der Karte wurden, so dass der Benutzer angemeldet ist In und aktiv würden sie für immer an diesen Threads festhalten.

Sie sollten versuchen, einen Lasttest auf einem Nicht-Produktionsserver durchzuführen, in dem Sie die normale Verwendung Ihrer App durch eine große Anzahl von Benutzern simulieren. Begrenzen Sie vielleicht sogar den Speicher des Servers noch niedriger als üblich.

Viel Glück, Speicherprobleme sind ein Schmerz, um sich zu verfolgen.

Sie sagen, dass Sie JVISUALVM bereits ausprobiert haben, um die Maschine zu inspizieren.Versuchen Sie es vielleicht noch einmal, wie folgt:

    .
  • Diese Zeit schauen Sie sich die Registerkarte "Sampler -> Memory" an.

  • Es sollte Ihnen sagen, welche (Typen von) Objekte den größten Speicher belegt.

  • Finden Sie dann heraus, wo solche Objekte normalerweise erstellt und entfernt werden.

  • Viele Male "seltsame Fehler können durch Java-Agenten verursacht werden, die in den JVM eingesteckt sind. Wenn Sie ein Agenten laufen lassen (z. B. Jrebel / Liveebel, Newrelic, JPROFILER), versuchen Sie, ohne sie zuerst zu laufen.
  • seltsame Dinge können auch passieren, wenn Sie JVM mit nicht standardmäßigen Parametern (-XX) ausführen; Bestimmte Kombinationen sind bekannt, um Probleme zu verursachen; Welche Parameter verwenden Sie derzeit?
  • Speicherleck kann auch in Magnolia selbst sein, haben Sie versucht, "Magnolia-Leck" versucht? Verwenden Sie 3RD-Party-Magnolienmodule? Wenn möglich, versuchen Sie, sie zu deaktivieren / abzubauen.

Das Problem kann mit nur einem Teil Ihres Anschlusses angeschlossen werden. Sie können versuchen, das Problem erneut zu reproduzieren, indem Sie Ihre Zugriffsprotokolle auf Ihrem Staging- / Entwicklungsserver wiederholen.

Wenn nichts anderes funktioniert, wenn ich es wäre, würde ich Folgendes tun: - versuchen, das Problem auf einer "leeren" Magnolias-Instanz (ohne einen von meinem Code) zu replizieren - versuchen, das Problem auf einer "leeren" Magnolias-Instanz (ohne Drittermodule) zu replizieren - versuchen, alle Software (Magnolia, 3RD-Party-Module, JVM) zu aktualisieren - Versuchen Sie endlich, die Produktionsstätte mit Yourkit auszuführen und versuchen, das Leck zu finden

Meine Vermutung ist, dass Sie ein automatisiertes Importlauf läuft, der ein Beispiel für ImportHandler ruft.Dieser Handler ist so konfiguriert, dass er eine Sicherung aller Knoten erstellt, die er aktualisieren wirdaus dem Gedächtnis.Versuchen Sie herauszufinden, welcher Importjob es ist, und deaktivieren Sie das Backup dafür.

hth, Jan

Es scheint, dass Ihre Speicherlecks von Ihren Arrays ausgehen.Der Garbage Collector hat Probleme, Objektinstanzen zu identifizieren, die aus Arrays entfernt wurden und daher nicht zur Speicherfreigabe gesammelt werden.Mein Rat ist, wenn Sie ein Objekt aus einem Array entfernen, weisen Sie ihm die Position des vorherigen Objekts zu null, Daher kann der Garbage Collector erkennen, dass es sich um einen handelt null Gegenstand und entfernen Sie ihn.Ich bezweifle, dass dies genau Ihr Problem ist, aber es ist immer gut, diese Dinge zu wissen und zu prüfen, ob dies Ihr Problem ist.

Es ist auch sinnvoll, ihm eine Objektinstanz zuzuweisen null wenn Sie es entfernen/säubern müssen.Dies liegt daran, dass die finalize() Die Methode ist lückenhaft und böse und wird manchmal vom Garbage Collector nicht aufgerufen.Die beste Lösung hierfür besteht darin, es (oder eine andere ähnliche Methode) selbst aufzurufen.Auf diese Weise können Sie sicher sein, dass die Müllbereinigung erfolgreich durchgeführt wurde.Wie Joshua Bloch in seinem Buch sagte:Effective Java, 2. Auflage, Punkt 7, Seite 27:Vermeiden Sie Finalizer.„Finalizer sind unvorhersehbar, oft gefährlich und im Allgemeinen unnötig.“Sie können den Abschnitt sehen Hier.

Da kein Code angezeigt wird, kann ich nicht erkennen, ob eine dieser Methoden nützlich sein kann, aber es lohnt sich trotzdem, diese Dinge zu wissen.Ich hoffe, diese Tipps helfen Ihnen!

Wie oben empfohlen, ich würde mich mit den Devs of Magnolia in Verbindung setzen, aber inzwischen:

Sie erhalten diesen Fehler, weil das GC nicht viel auf einem Lauf sammelt

Der gleichzeitige Kollektor wirft einen OutofMemoryError bei zu viel Die Zeit wird in der Müllsammlung ausgegeben: Wenn mehr als 98% der Die Gesamtzeit wird in der Müllsammlung und weniger als 2% des Heaps ausgegeben wird wiederhergestellt, ein OutofMemoryError wird geworfen.

Da Sie die Implementierung nicht ändern können, würde ich empfehlen, die Konfiguration des GC zu ändern, auf eine Weise, die weniger häufig läuft, so dass es weniger wahrscheinlich ist, auf diese Weise zu versagen.

Hier ist ein Beispiel-Config, nur um Sie mit den Parametern zu beginnen, müssen Sie Ihren Sweet-Spot herausfinden. Die Protokolle des GC werden wahrscheinlich dabei helfen

Meine VM-Parameter sind wie folgt: -Xms= 6g. -XMX= 6G. -Xx: maxpermsise= 1g -Xx: Zeitung= 2g -Xx: maxtenuringhreshold= 8 -Xx: Survivivorratio= 7 -Xx: + useconcmarkSeepgc -Xx: + cmsclassunloadingingEnabled -Xx: + cmsspermgenensweeepingEnabled -XX: CMSInitiatingAccupancyFraction= 60 -Xx: + heapdumponoutofMemoryError -Xx: + printgcdetails -XX: + PrintgcTimestamps -XX: + druckteuringingdistribution -Xloggc: Protokolle / gc.log

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top