Suche nach Java-Speicherlecks
Veröffentlicht: 2022-03-11Unerfahrene Programmierer denken oft, dass Javas automatische Garbage Collection sie völlig von der Sorge um die Speicherverwaltung befreit. Dies ist ein weit verbreiteter Irrglaube: Während der Garbage Collector sein Bestes gibt, ist es durchaus möglich, dass selbst der beste Programmierer Opfer von lähmenden Speicherlecks wird. Lassen Sie mich erklären.
Ein Speicherverlust tritt auf, wenn nicht mehr benötigte Objektreferenzen unnötigerweise beibehalten werden. Diese Lecks sind schlecht. Zum einen belasten sie Ihre Maschine unnötig, da Ihre Programme immer mehr Ressourcen verbrauchen. Erschwerend kommt hinzu, dass die Erkennung dieser Lecks schwierig sein kann: Die statische Analyse hat oft Schwierigkeiten, diese redundanten Referenzen genau zu identifizieren, und vorhandene Leckerkennungstools verfolgen und melden feinkörnige Informationen zu einzelnen Objekten, was zu schwer interpretierbaren und ungenauen Ergebnissen führt.
Mit anderen Worten, Lecks sind entweder zu schwer zu identifizieren oder mit Begriffen identifiziert, die zu spezifisch sind, um nützlich zu sein.
Es gibt tatsächlich vier Kategorien von Gedächtnisproblemen mit ähnlichen und sich überschneidenden Symptomen, aber unterschiedlichen Ursachen und Lösungen:
Leistung : Wird normalerweise mit übermäßiger Objekterstellung und -löschung, langen Verzögerungen bei der Garbage Collection, übermäßigem Wechseln von Betriebssystemseiten und mehr in Verbindung gebracht.
Ressourcenbeschränkungen : Tritt auf, wenn entweder zu wenig Speicher verfügbar ist oder Ihr Speicher zu fragmentiert ist, um ein großes Objekt zuzuweisen – dies kann nativ oder häufiger Java-Heap-bezogen sein.
Java Heap Leaks : Das klassische Speicherleck, bei dem Java-Objekte kontinuierlich erstellt werden, ohne freigegeben zu werden. Dies wird normalerweise durch latente Objektreferenzen verursacht.
Native Memory Leaks : verbunden mit jeder kontinuierlich wachsenden Speicherauslastung, die außerhalb des Java-Heaps liegt, wie z. B. Zuweisungen durch JNI-Code, Treiber oder sogar JVM-Zuweisungen.
In diesem Lernprogramm zur Speicherverwaltung konzentriere ich mich auf Lecks in Java-Heaps und skizziere einen Ansatz zum Erkennen solcher Lecks auf der Grundlage von Java VisualVM -Berichten und Verwenden einer visuellen Schnittstelle zum Analysieren von auf Java-Technologie basierenden Anwendungen, während sie ausgeführt werden.
Aber bevor Sie Speicherlecks verhindern und finden können, sollten Sie verstehen, wie und warum sie auftreten. ( Hinweis: Wenn Sie die Feinheiten von Speicherlecks gut im Griff haben, können Sie weitermachen. )
Speicherlecks: Eine Einführung
Stellen Sie sich für den Anfang Memory Leakage als Krankheit und Javas OutOfMemoryError
(OOM, kurz gesagt) als Symptom vor. Aber wie bei jeder Krankheit implizieren nicht alle OOMs unbedingt Speicherlecks : Ein OOM kann aufgrund der Erzeugung einer großen Anzahl lokaler Variablen oder anderer solcher Ereignisse auftreten. Andererseits manifestieren sich nicht alle Speicherlecks zwangsläufig als OOMs , insbesondere bei Desktop-Anwendungen oder Client-Anwendungen (die ohne Neustart nicht sehr lange ausgeführt werden).
Warum sind diese Lecks so schlimm? Unter anderem beeinträchtigen Speicherblöcke während der Programmausführung häufig die Systemleistung im Laufe der Zeit, da zugewiesene, aber nicht verwendete Speicherblöcke ausgelagert werden müssen, sobald das System keinen freien physischen Speicher mehr hat. Schließlich kann ein Programm sogar seinen verfügbaren virtuellen Adressraum erschöpfen, was zum OOM führt.
Entschlüsseln des OutOfMemoryError
Wie oben erwähnt, ist OOM ein häufiger Hinweis auf ein Speicherleck. Im Wesentlichen wird der Fehler ausgelöst, wenn nicht genügend Speicherplatz vorhanden ist, um ein neues Objekt zuzuweisen. So sehr es sich auch anstrengt, der Garbage Collector findet nicht den nötigen Platz und der Heap lässt sich nicht weiter erweitern. Somit entsteht ein Fehler zusammen mit einem Stack-Trace.
Der erste Schritt bei der Diagnose Ihres OOM besteht darin, festzustellen, was der Fehler tatsächlich bedeutet. Das klingt offensichtlich, aber die Antwort ist nicht immer so klar. Beispiel: Wird OOM angezeigt, weil der Java-Heap voll ist oder weil der native Heap voll ist? Um Ihnen bei der Beantwortung dieser Frage zu helfen, analysieren wir einige der möglichen Fehlermeldungen:
java.lang.OutOfMemoryError: Java heap space
java.lang.OutOfMemoryError: PermGen space
java.lang.OutOfMemoryError: Requested array size exceeds VM limit
java.lang.OutOfMemoryError: request <size> bytes for <reason>. Out of swap space?
java.lang.OutOfMemoryError: <reason> <stack trace> (Native method)
"Java heap space"
Diese Fehlermeldung weist nicht unbedingt auf einen Speicherverlust hin. Tatsächlich kann das Problem so einfach wie ein Konfigurationsproblem sein.
Zum Beispiel war ich für die Analyse einer Anwendung verantwortlich, die ständig diese Art von OutOfMemoryError
produzierte. Nach einigen Nachforschungen fand ich heraus, dass der Übeltäter eine Array-Instanziierung war, die zu viel Speicher beanspruchte; In diesem Fall war es nicht die Schuld der Anwendung, sondern der Anwendungsserver verließ sich auf die Standard-Heap-Größe, die zu klein war. Ich habe das Problem gelöst, indem ich die Speicherparameter der JVM angepasst habe.
In anderen Fällen, insbesondere bei langlebigen Anwendungen, kann die Nachricht ein Hinweis darauf sein, dass wir unbeabsichtigt Verweise auf Objekte halten und den Garbage Collector daran hindern, sie zu bereinigen. Dies ist das Java - Äquivalent eines Speicherlecks . ( Hinweis: APIs, die von einer Anwendung aufgerufen werden, können auch unbeabsichtigt Objektreferenzen enthalten. )
Eine weitere potenzielle Quelle dieser „Java-Heap-Space“-OOMs ergibt sich aus der Verwendung von Finalizern . Wenn eine Klasse über eine finalize
-Methode verfügt, wird der Speicherplatz von Objekten dieses Typs nicht zum Zeitpunkt der Garbage-Collection zurückgewonnen. Stattdessen werden die Objekte nach der Garbage Collection in die Warteschlange für die Finalisierung gestellt, die später erfolgt. In der Sun-Implementierung werden Finalizer von einem Daemon-Thread ausgeführt. Wenn der Finalizer-Thread nicht mit der Finalisierungswarteschlange Schritt halten kann, könnte sich der Java-Heap füllen und ein OOM ausgelöst werden.
„PermGen-Raum“
Diese Fehlermeldung weist darauf hin, dass die permanente Generierung voll ist. Die permanente Generierung ist der Bereich des Heaps, der Klassen- und Methodenobjekte speichert. Wenn eine Anwendung eine große Anzahl von Klassen lädt, muss die Größe der permanenten Generierung möglicherweise mit der Option -XX:MaxPermSize
erhöht werden.
Internierte java.lang.String
Objekte werden ebenfalls in der permanenten Generierung gespeichert. Die Klasse java.lang.String
verwaltet einen Pool von Zeichenfolgen. Wenn die interne Methode aufgerufen wird, überprüft die Methode den Pool, um zu sehen, ob eine äquivalente Zeichenfolge vorhanden ist. Wenn ja, wird es von der internen Methode zurückgegeben; wenn nicht, wird die Zeichenfolge dem Pool hinzugefügt. Genauer gesagt gibt die Methode java.lang.String.intern
die kanonische Darstellung eines Strings zurück; Das Ergebnis ist ein Verweis auf dieselbe Klasseninstanz, die zurückgegeben würde, wenn diese Zeichenfolge als Literal angezeigt würde. Wenn eine Anwendung eine große Anzahl von Zeichenfolgen interniert, müssen Sie möglicherweise die Größe der permanenten Generierung erhöhen.
Hinweis: Sie können den jmap -permgen
, um Statistiken zur permanenten Generierung zu drucken, einschließlich Informationen zu internalisierten String-Instanzen.
„Angeforderte Array-Größe überschreitet VM-Grenze“
Dieser Fehler weist darauf hin, dass die Anwendung (oder die von dieser Anwendung verwendeten APIs) versucht haben, ein Array zuzuweisen, das größer als die Heap-Größe ist. Wenn beispielsweise eine Anwendung versucht, ein Array von 512 MB zuzuweisen, die maximale Heap-Größe jedoch 256 MB beträgt, wird ein OOM mit dieser Fehlermeldung ausgegeben. In den meisten Fällen ist das Problem entweder ein Konfigurationsproblem oder ein Fehler, der entsteht, wenn eine Anwendung versucht, ein riesiges Array zuzuweisen.
„Fordere <Größe> Bytes für <Grund> an. Kein Swap Space mehr?“
Diese Nachricht scheint ein OOM zu sein. Die HotSpot-VM löst jedoch diese offensichtliche Ausnahme aus, wenn eine Zuordnung vom nativen Heap fehlgeschlagen ist und der native Heap möglicherweise fast erschöpft ist. Die Nachricht enthält die Größe (in Bytes) der fehlgeschlagenen Anforderung und den Grund für die Speicheranforderung. In den meisten Fällen ist <reason> der Name des Quellmoduls, das einen Zuordnungsfehler meldet.
Wenn diese Art von OOM ausgelöst wird, müssen Sie möglicherweise Dienstprogramme zur Fehlerbehebung auf Ihrem Betriebssystem verwenden, um das Problem weiter zu diagnostizieren. In einigen Fällen hängt das Problem möglicherweise nicht einmal mit der Anwendung zusammen. Dieser Fehler kann beispielsweise angezeigt werden, wenn:
Das Betriebssystem ist mit unzureichendem Auslagerungsspeicher konfiguriert.
Ein anderer Prozess auf dem System verbraucht alle verfügbaren Speicherressourcen.
Es ist auch möglich, dass die Anwendung aufgrund eines nativen Lecks fehlgeschlagen ist (z. B. wenn ein Teil des Anwendungs- oder Bibliothekscodes kontinuierlich Speicher zuweist, ihn aber nicht an das Betriebssystem freigibt).
<Grund> <Stack-Trace> (Native Methode)
Wenn diese Fehlermeldung angezeigt wird und der oberste Frame Ihres Stack-Trace eine native Methode ist, ist bei dieser nativen Methode ein Zuordnungsfehler aufgetreten. Der Unterschied zwischen dieser Nachricht und der vorherigen besteht darin, dass der Java-Speicherzuweisungsfehler in einer JNI- oder nativen Methode und nicht im Java-VM-Code erkannt wurde.
Wenn diese Art von OOM ausgelöst wird, müssen Sie möglicherweise Dienstprogramme des Betriebssystems verwenden, um das Problem weiter zu diagnostizieren.
Anwendungsabsturz ohne OOM
Gelegentlich kann es vorkommen, dass eine Anwendung kurz nach einem Zuordnungsfehler vom nativen Heap abstürzt. Dies tritt auf, wenn Sie nativen Code ausführen, der nicht nach Fehlern sucht, die von Speicherzuweisungsfunktionen zurückgegeben werden.
Beispielsweise gibt der malloc
-Systemaufruf NULL
zurück, wenn kein Speicher verfügbar ist. Wenn die Rückgabe von malloc
nicht überprüft wird, stürzt die Anwendung möglicherweise ab, wenn sie versucht, auf einen ungültigen Speicherort zuzugreifen. Abhängig von den Umständen kann es schwierig sein, diese Art von Problem zu lokalisieren.
In manchen Fällen reichen die Informationen aus dem Fatal-Error-Log oder dem Crash-Dump aus. Wenn die Ursache eines Absturzes in einer fehlenden Fehlerbehandlung bei einigen Speicherzuordnungen festgestellt wird, müssen Sie den Grund für diesen Zuordnungsfehler finden. Wie bei jedem anderen nativen Heap-Problem ist das System möglicherweise mit unzureichendem Auslagerungsspeicher konfiguriert, ein anderer Prozess verbraucht möglicherweise alle verfügbaren Speicherressourcen usw.
Diagnose von Lecks
In den meisten Fällen erfordert die Diagnose von Speicherlecks sehr detaillierte Kenntnisse der betreffenden Anwendung. Warnung: Der Prozess kann langwierig und iterativ sein.
Unsere Strategie zur Suche nach Speicherlecks wird relativ einfach sein:
Identifizieren Sie Symptome
Aktivieren Sie die ausführliche Garbage Collection
Profilerstellung aktivieren
Analysieren Sie die Spur
1. Identifizieren Sie die Symptome
Wie bereits erwähnt, löst der Java-Prozess in vielen Fällen schließlich eine OOM-Laufzeitausnahme aus, ein deutlicher Hinweis darauf, dass Ihre Speicherressourcen erschöpft sind. In diesem Fall müssen Sie zwischen einer normalen Speichererschöpfung und einem Leck unterscheiden. Analysieren Sie die Nachricht des OOM und versuchen Sie, den Schuldigen auf der Grundlage der oben bereitgestellten Diskussionen zu finden.
Wenn eine Java-Anwendung mehr Speicherplatz anfordert, als der Runtime-Heap bietet, kann dies häufig auf ein schlechtes Design zurückzuführen sein. Wenn eine Anwendung beispielsweise mehrere Kopien eines Bildes erstellt oder eine Datei in ein Array lädt, wird der Speicherplatz knapp, wenn das Bild oder die Datei sehr groß ist. Dies ist eine normale Ressourcenerschöpfung. Die Anwendung funktioniert wie vorgesehen (obwohl dieses Design eindeutig dämlich ist).
Wenn eine Anwendung jedoch ihre Speicherauslastung ständig erhöht, während sie die gleiche Art von Daten verarbeitet, liegt möglicherweise ein Speicherleck vor.
2. Aktivieren Sie die ausführliche Garbage Collection
Eine der schnellsten Möglichkeiten, um zu bestätigen, dass Sie tatsächlich ein Speicherleck haben, besteht darin, die ausführliche Garbage Collection zu aktivieren. Speicherbeschränkungsprobleme können normalerweise identifiziert werden, indem Muster in der verbosegc
Ausgabe untersucht werden.
Insbesondere das Argument -verbosegc
ermöglicht es Ihnen, jedes Mal, wenn der Garbage-Collection-Prozess (GC) gestartet wird, eine Ablaufverfolgung zu generieren. Das bedeutet, dass während der Speicherbereinigung zusammenfassende Berichte als Standardfehler ausgegeben werden, die Ihnen einen Eindruck davon vermitteln, wie Ihr Speicher verwaltet wird.

Hier ist eine typische Ausgabe, die mit der Option –verbosegc
generiert wird:
Jeder Block (oder Zeilenabschnitt) in dieser GC-Trace-Datei ist in aufsteigender Reihenfolge nummeriert. Um dieser Ablaufverfolgung einen Sinn zu geben, sollten Sie aufeinanderfolgende Allocation Failure-Stanzas betrachten und nach freigegebenem Speicher (Byte und Prozentsatz) suchen, der im Laufe der Zeit abnimmt, während der Gesamtspeicher (hier 19725304) zunimmt. Dies sind typische Anzeichen einer Gedächtnisschwäche.
3. Aktivieren Sie die Profilerstellung
Verschiedene JVMs bieten verschiedene Möglichkeiten zum Generieren von Ablaufverfolgungsdateien, um die Heap-Aktivität widerzuspiegeln, die normalerweise detaillierte Informationen über den Typ und die Größe von Objekten enthalten. Dies wird als Heap-Profilerstellung bezeichnet.
4. Analysieren Sie die Ablaufverfolgung
Dieser Beitrag konzentriert sich auf die von Java VisualVM generierte Ablaufverfolgung. Traces können in verschiedenen Formaten vorliegen, da sie von verschiedenen Java-Tools zur Erkennung von Speicherlecks generiert werden können, aber die Idee dahinter ist immer dieselbe: Finden Sie einen Block von Objekten im Heap, der nicht dort sein sollte, und bestimmen Sie, ob sich diese Objekte anhäufen statt loszulassen. Von besonderem Interesse sind transiente Objekte, von denen bekannt ist, dass sie jedes Mal zugewiesen werden, wenn ein bestimmtes Ereignis in der Java-Anwendung ausgelöst wird. Das Vorhandensein vieler Objektinstanzen, die nur in kleinen Mengen vorhanden sein sollten, weist im Allgemeinen auf einen Anwendungsfehler hin.
Schließlich erfordert das Beheben von Speicherlecks, dass Sie Ihren Code gründlich überprüfen. Es kann sehr hilfreich sein, etwas über die Art des undichten Objekts zu erfahren und die Fehlersuche erheblich zu beschleunigen.
Wie funktioniert Garbage Collection in der JVM?
Bevor wir mit der Analyse einer Anwendung mit einem Speicherleckproblem beginnen, schauen wir uns zunächst an, wie die Garbage Collection in der JVM funktioniert.
Die JVM verwendet eine Form von Garbage Collector namens Tracing Collector , die im Wesentlichen so funktioniert, dass sie die Welt um sie herum anhält, alle Root-Objekte (Objekte, auf die direkt von laufenden Threads verwiesen wird) markiert und ihren Referenzen folgt, wobei sie jedes Objekt markiert, das sie auf dem Weg sieht.
Java implementiert einen sogenannten Generational Garbage Collector, der auf der Annahme der Generationshypothese basiert, die besagt, dass die Mehrheit der erstellten Objekte schnell verworfen werden und Objekte, die nicht schnell gesammelt werden, wahrscheinlich noch eine Weile vorhanden sind .
Basierend auf dieser Annahme partitioniert Java Objekte in mehrere Generationen. Hier ist eine visuelle Interpretation:
Junge Generation - Hier fangen die Objekte an. Es hat zwei Untergenerationen:
Eden Space – Objekte beginnen hier. Die meisten Objekte werden im Eden Space erstellt und zerstört. Hier führt der GC Minor GCs aus , bei denen es sich um optimierte Garbage Collections handelt. Wenn ein Minor GC durchgeführt wird, werden alle Verweise auf Objekte, die noch benötigt werden, in einen der überlebenden Spaces (S0 oder S1) migriert.
Survivor Space (S0 und S1) – Objekte, die Eden überleben, landen hier. Es gibt zwei davon, und nur einer wird zu einem bestimmten Zeitpunkt verwendet (es sei denn, wir haben ein ernsthaftes Speicherleck). Einer wird als leer und der andere als aktiv bezeichnet, abwechselnd mit jedem GC-Zyklus.
Tenured Generation – Dieser Bereich, auch bekannt als die alte Generation (altes Feld in Abb. 2), enthält ältere Objekte mit längerer Lebensdauer (aus den Überlebensfeldern verschoben, wenn sie lange genug leben). Wenn dieser Platz gefüllt ist, führt der GC einen Full GC durch, der mehr Leistung kostet. Wenn dieser Speicherplatz unbegrenzt wächst, gibt die JVM einen
OutOfMemoryError - Java heap space
.Permanent Generation – Eine dritte Generation, die eng mit der Tenured Generation verwandt ist, ist die Permanent Generation etwas Besonderes, da sie Daten enthält, die von der virtuellen Maschine benötigt werden, um Objekte zu beschreiben, die keine Entsprechung auf der Ebene der Java-Sprache haben. Beispielsweise werden Objekte, die Klassen und Methoden beschreiben, in der permanenten Generierung gespeichert.
Java ist intelligent genug, um auf jede Generation unterschiedliche Garbage-Collection-Methoden anzuwenden. Die junge Generation wird mit einem Tracing-Kopiersammler namens Parallel New Collector behandelt. Dieser Sammler hält die Welt an, aber weil die junge Generation im Allgemeinen klein ist, ist die Pause kurz.
Weitere Informationen zu den JVM-Generationen und ihrer Funktionsweise im Detail finden Sie in der Dokumentation zur Speicherverwaltung in der Java HotSpot Virtual Machine-Dokumentation.
Erkennen eines Speicherlecks
Um Speicherlecks zu finden und zu beseitigen, benötigen Sie die richtigen Speicherleck-Tools. Es ist an der Zeit, ein solches Leck mit Java VisualVM zu erkennen und zu beseitigen.
Remote-Profiling des Heaps mit Java VisualVM
VisualVM ist ein Tool, das eine visuelle Schnittstelle zum Anzeigen detaillierter Informationen zu auf Java-Technologie basierenden Anwendungen bereitstellt, während sie ausgeführt werden.
Mit VisualVM können Sie Daten anzeigen, die sich auf lokale Anwendungen und Anwendungen beziehen, die auf Remote-Hosts ausgeführt werden. Sie können auch Daten über JVM-Softwareinstanzen erfassen und die Daten auf Ihrem lokalen System speichern.
Um von allen Funktionen von Java VisualVM zu profitieren, sollten Sie die Java Platform, Standard Edition (Java SE) Version 6 oder höher ausführen.
Aktivieren der Remote-Verbindung für die JVM
In einer Produktionsumgebung ist es oft schwierig, auf den tatsächlichen Computer zuzugreifen, auf dem unser Code ausgeführt wird. Glücklicherweise können wir unsere Java-Anwendung aus der Ferne profilieren.
Zuerst müssen wir uns JVM-Zugriff auf dem Zielcomputer gewähren. Erstellen Sie dazu eine Datei namens jstatd.all.policy mit folgendem Inhalt:
grant codebase "file:${java.home}/../lib/tools.jar" { permission java.security.AllPermission; };
Sobald die Datei erstellt wurde, müssen wir Remote-Verbindungen zur Ziel-VM mit dem Tool jstatd – Virtual Machine jstat Daemon wie folgt aktivieren:
jstatd -p <PORT_NUMBER> -J-Djava.security.policy=<PATH_TO_POLICY_FILE>
Zum Beispiel:
jstatd -p 1234 -J-Djava.security.policy=D:\jstatd.all.policy
Wenn jstatd in der Ziel-VM gestartet ist, können wir eine Verbindung zum Zielcomputer herstellen und die Anwendung mit Speicherverlustproblemen aus der Ferne profilieren.
Herstellen einer Verbindung zu einem Remote-Host
Öffnen Sie auf dem Clientcomputer eine Eingabeaufforderung und jvisualvm
ein, um das VisualVM-Tool zu öffnen.
Als nächstes müssen wir einen Remote-Host in VisualVM hinzufügen. Da die Ziel-JVM aktiviert ist, um Remote-Verbindungen von einem anderen Computer mit J2SE 6 oder höher zuzulassen, starten wir das Java VisualVM-Tool und stellen eine Verbindung zum Remote-Host her. Wenn die Verbindung mit dem Remote-Host erfolgreich war, sehen wir die Java-Anwendungen, die in der Ziel-JVM ausgeführt werden, wie hier zu sehen:
Um einen Speicherprofiler für die Anwendung auszuführen, doppelklicken wir einfach auf seinen Namen im Seitenbereich.
Nachdem wir nun alle mit einem Speicheranalysator ausgestattet sind, untersuchen wir eine Anwendung mit einem Speicherleckproblem, das wir MemLeak nennen.
MemLeak
Natürlich gibt es eine Reihe von Möglichkeiten, Speicherlecks in Java zu erzeugen. Der Einfachheit halber werden wir eine Klasse als Schlüssel in einer HashMap
definieren, aber wir werden die Methoden equals() und hashcode() nicht definieren.
Eine HashMap ist eine Hash-Tabellenimplementierung für die Map-Schnittstelle und definiert als solche die Grundkonzepte von Schlüssel und Wert: Jeder Wert bezieht sich auf einen eindeutigen Schlüssel, wenn also der Schlüssel für ein bestimmtes Schlüssel-Wert-Paar bereits in der vorhanden ist HashMap, sein aktueller Wert wird ersetzt.
Es ist zwingend erforderlich, dass unsere Schlüsselklasse eine korrekte Implementierung der Methoden equals()
und hashcode()
. Ohne sie gibt es keine Garantie dafür, dass ein guter Schlüssel generiert wird.
Indem wir die Methoden equals()
und hashcode()
nicht definieren, fügen wir der HashMap immer wieder denselben Schlüssel hinzu, und anstatt den Schlüssel wie vorgesehen zu ersetzen, wächst die HashMap kontinuierlich, erkennt diese identischen Schlüssel nicht und gibt einen OutOfMemoryError
.
Hier ist die MemLeak-Klasse:
package com.post.memory.leak; import java.util.Map; public class MemLeak { public final String key; public MemLeak(String key) { this.key =key; } public static void main(String args[]) { try { Map map = System.getProperties(); for(;;) { map.put(new MemLeak("key"), "value"); } } catch(Exception e) { e.printStackTrace(); } } }
Hinweis: Das Speicherleck ist nicht auf die Endlosschleife in Zeile 14 zurückzuführen: Die Endlosschleife kann zu einer Ressourcenerschöpfung führen, aber nicht zu einem Speicherleck. Wenn wir die Methoden equals()
und hashcode()
richtig implementiert hätten, würde der Code auch mit der Endlosschleife gut laufen, da wir nur ein Element innerhalb der HashMap hätten.
(Für diejenigen, die daran interessiert sind, sind hier einige alternative Mittel, um (absichtlich) Lecks zu erzeugen.)
Verwenden von Java-VisualVM
Mit Java VisualVM können wir den Speicher des Java Heap überwachen und feststellen, ob sein Verhalten auf ein Speicherleck hinweist.
Hier ist eine grafische Darstellung des Java-Heap-Analyzers von MemLeak kurz nach der Initialisierung (erinnern Sie sich an unsere Diskussion der verschiedenen Generationen):
Nach nur 30 Sekunden ist die alte Generation fast voll, was darauf hindeutet, dass die alte Generation selbst bei einem vollen GC ständig wächst, ein klares Zeichen für ein Speicherleck.
Eine Möglichkeit, die Ursache dieses Lecks zu ermitteln, ist im folgenden Bild (zum Vergrößern klicken ) dargestellt, das mit Java VisualVM mit einem Heapdump generiert wurde . Hier sehen wir, dass sich 50 % der Hashtable$Entry-Objekte im Heap befinden , während die zweite Zeile uns auf die MemLeak -Klasse verweist. Daher wird das Speicherleck durch eine Hash-Tabelle verursacht, die innerhalb der MemLeak -Klasse verwendet wird.
Beobachten Sie schließlich den Java-Heap direkt nach unserem OutOfMemoryError
, in dem die Young- und Old-Generationen vollständig voll sind .
Fazit
Speicherlecks gehören zu den am schwierigsten zu behebenden Problemen in Java-Anwendungen, da die Symptome vielfältig und schwer zu reproduzieren sind. Hier haben wir einen schrittweisen Ansatz zum Auffinden von Speicherlecks und zum Identifizieren ihrer Quellen skizziert. Aber lesen Sie vor allem Ihre Fehlermeldungen genau durch und achten Sie auf Ihre Stack-Traces – nicht alle Lecks sind so einfach, wie sie erscheinen.
Blinddarm
Neben Java VisualVM gibt es mehrere andere Tools, die Speicherlecks erkennen können. Viele Leckdetektoren arbeiten auf Bibliotheksebene, indem sie Aufrufe von Speicherverwaltungsroutinen abfangen. Beispielsweise ist HPROF
ein einfaches Befehlszeilentool, das mit der Java 2 Platform Standard Edition (J2SE) für Heap- und CPU-Profiling gebündelt ist. Die Ausgabe von HPROF
kann direkt analysiert oder als Eingabe für andere Tools wie JHAT
. Wenn wir mit Java 2 Enterprise Edition (J2EE)-Anwendungen arbeiten, gibt es eine Reihe von Heap-Dump-Analyselösungen, die benutzerfreundlicher sind, wie z. B. IBM Heapdumps für Websphere-Anwendungsserver.