HOWTO · Java

Finden Sie Speicherlecks in Java

Dieser Artikel erklärt, wie man Speicherlecks in Java findet.

Auf dieser Seite

Ein Java-Speicherleck liegt vor, wenn eine Anwendung Objekte erreichbar hält, obwohl sie nicht mehr benötigt werden. Der Garbage Collector gibt nicht mehr erreichbare Objekte frei, kann aber kein Objekt freigeben, das über eine aktive Referenz weiterhin erreichbar ist. Wenn sich solche Objekte ansammeln, können sie den Speicherverbrauch erhöhen und schließlich zu einem java.lang.OutOfMemoryError beitragen. Ein hoher Speicherverbrauch allein belegt kein Leck. Untersuchen Sie, ob die Menge der erreichbaren Objekte bei wiederholten, vergleichbaren Arbeitsabläufen wächst.

Was ist ein Java-Speicherleck?

Java verwaltet den Speicher für Objekte automatisch. Ein Leck bedeutet daher meist, dass das Programm Referenzen länger als beabsichtigt festhält, und nicht, dass Speicher manuell freigegeben werden muss. Ein Objekt kann für die Anwendung logisch überflüssig sein und dennoch über ein statisches Feld, einen langlebigen Dienst, eine Sammlung oder ein anderes Objekt erreichbar bleiben. Der Garbage Collector erhält solche erreichbaren Objekte korrekterweise. Diese Unterscheidung hilft, unerwünschtes Festhalten von einem vorübergehenden Allokationsanstieg oder normalem Heap-Wachstum abzugrenzen.

Häufige Ursachen für Java-Speicherlecks

Achten Sie auf Fehler bei Besitzverhältnissen und Lebenszyklen, durch die Referenzen länger als nötig bestehen bleiben. Häufige Beispiele sind unbegrenzte Caches, Sitzungsobjekte, die nach dem Ende einer Sitzung erhalten bleiben, Sammlungen, denen fortlaufend Einträge hinzugefügt werden, ohne alte zu entfernen, Listener, die nach dem Ende ihres Nutzungszwecks registriert bleiben, sowie benutzerdefinierte Datenstrukturen, die veraltete Knoten festhalten. Das sind Ansatzpunkte für die Untersuchung, aber kein Beweis dafür, dass ein bestimmtes Objekt ein Leck verursacht. Prüfen Sie, wem die jeweilige Referenz gehört und wann dieser Eigentümer sie freigeben sollte.

Ein praktischer Ablauf zum Auffinden eines Java-Speicherlecks

Beginnen Sie damit, dieselbe Arbeitslast in wiederholbaren Durchläufen auszuführen. Nachdem jeder Durchlauf abgeschlossen ist und der Garbage Collector Gelegenheit zur Arbeit hatte, erfassen Sie den Ausgangswert des belegten Speichers. Vergleichen Sie gleichartige Zeitpunkte statt nicht zusammengehöriger Spitzen während der Speicherallokation. Vergleichen Sie den nach der Garbage Collection belegten Speicher über wiederholte, gleichartige Arbeitszyklen hinweg. Ein steigender Ausgangswert ist ein Anlass zur Untersuchung, aber kein Beweis für ein Leck.

Diagramm mit gleichartigen Arbeitszyklen, steigendem Speicherverbrauch nach der Garbage Collection, dem Vergleich von Heap-Dumps, der Rückverfolgung gehaltener Referenzen, einer Korrektur und erneuten Tests.

Untersuchungsablauf, um eine wachsende Menge gehaltener Objekte von vorübergehenden Allokationen zu unterscheiden.

Steigt der Ausgangswert weiter, erfassen Sie Heap-Daten an vergleichbaren Punkten der Arbeitslast. Vergleichen Sie Objektzahlen und gehaltene Speichergrößen, verfolgen Sie verdächtige Objekte zu den Referenzen zurück, die sie erreichbar halten, und ermitteln Sie den Eigentümer mit dem zu langen Lebenszyklus. Nehmen Sie eine gezielte Änderung vor, führen Sie dieselbe Arbeitslast erneut aus und vergleichen Sie dieselben Messwerte. Eine einzelne Messung kann irreführend sein. Berücksichtigen Sie daher Schwankungen der Arbeitslast und das normale Anwendungsverhalten, bevor Sie auf ein Leck schließen.

Führen Sie dieses eigenständige Java-Beispiel aus, um zu sehen, wie das Entfernen von Einträgen die Referenzen der Sammlung auf diese Einträge freigibt:

Der Code und die angezeigten Bezeichnungen oder Ausgaben in diesem Abschnitt bleiben auf Englisch.

import java.util.HashMap;
import java.util.Map;

public class CacheLifecycle {
    public static void main(String[] args) {
        Map<Integer, byte[]> cache = new HashMap<>();
        for (int i = 0; i < 3; i++) {
            cache.put(i, new byte[1024]);
        }
        System.out.println("Entries before cleanup: " + cache.size());
        cache.clear();
        System.out.println("Entries after cleanup: " + cache.size());
    }
}

Dieses kleine Beispiel veranschaulicht, wem eine Sammlung gehört und wie ihre Einträge bereinigt werden; es misst weder den Heap-Verbrauch noch weist es ein Leck in einer Produktionsumgebung nach. Entfernen Sie in einer echten Anwendung einen Eintrag nur dann, wenn sein Lebenszyklus oder die Cache-Richtlinie festlegt, dass er nicht mehr benötigt wird.

Heap-Dumps und gehaltene Objekte analysieren

Ein Heap-Dump hält Objekte und Referenzen zu einem bestimmten Zeitpunkt fest. Verwenden Sie ihn, um ungewöhnlich häufig vorkommende Objekttypen zu finden, ihre gehaltene Speichergröße zu prüfen und Referenzpfade zu langlebigen Wurzeln wie statischen Feldern oder Anwendungsdiensten zurückzuverfolgen. Vergleichen Sie Dumps nach gleichartigen Arbeitszyklen, um zu erkennen, welche Objektgruppen wachsen und erreichbar bleiben. Ein einzelner Dump zeigt einen Zustand, der untersucht werden sollte; für sich allein beweist er kein Leck. Prüfen Sie den vermuteten Verbleib der Objekte anhand des erwarteten Lebenszyklus der Anwendung.

Profiler und JVM-Monitoring-Werkzeuge verwenden

Monitoring kann zeigen, ob sich Heap-Nutzung, Allokationen oder das Verhalten der Garbage Collection im Laufe der Zeit ändern. Ein Profiler kann dabei helfen, Objektgruppen und Allokationsaktivitäten mit dem Anwendungsverhalten in Verbindung zu bringen. Eine anschließende Heap-Dump-Analyse kann aufdecken, warum Instanzen erreichbar bleiben. NetBeans Profiler, VisualVM und JProfiler sind Beispiele aus Java-Profiling-Abläufen, keine Empfehlungen zur aktuellen Verfügbarkeit oder zu bestimmten Funktionen. Wählen Sie ein Werkzeug, das zur untersuchten JVM und Umgebung passt, und betrachten Sie seine Diagramme und Warnungen als auszuwertende Hinweise, nicht als eigenständige Diagnose.

Garbage-Collection-Protokolle untersuchen

Die Option -verbose:gc von JVM ist ein Quellenbeispiel zum Aktivieren ausführlicher Protokolle zur Garbage Collection. Ob sie anwendbar ist und wie das resultierende Protokoll aussieht und zu interpretieren ist, hängt von der Version der JVM ab. Untersuchen Sie das Verhalten der Garbage Collection und den nach der Sammlung belegten Speicher über vergleichbare Arbeitsintervalle hinweg. Ein Protokoll kann Hinweise darauf geben, dass Speicher nach der Sammlung weiterhin belegt ist. Eine Zusammenfassung allein zeigt jedoch nicht, welche Referenzen Objekte festhalten, und belegt auch nicht die Ursache.

Warnungen von Eclipse zu Speicherlecks und ihre Grenzen verstehen

Warnungen von Eclipse können auf bestimmte Muster bei Ressourcen oder Referenzen im Code aufmerksam machen. Prüfen Sie jede Warnung im Kontext und verifizieren Sie den Lebenszyklus der betreffenden Objekte und Ressourcen. Eine IDE-Warnung ist ein unvollständiger Hinweis: Sie belegt weder, dass jede Warnung ein Speicherleck betrifft, noch beweist das Ausbleiben einer Warnung, dass die Anwendung leckfrei ist. Verwenden Sie die alte Kompatibilitätsangabe zu JDK 1.5 nicht als allgemeine Beschreibung des aktuellen Erkennungsverhaltens.

Die Korrektur überprüfen und gehaltenen Speicher reduzieren

Nachdem Sie den vermuteten Eigentümer oder den Bereinigungspfad geändert haben, führen Sie dieselbe Arbeitslast erneut aus und vergleichen Sie den nach der Garbage Collection belegten Speicher, die Objektzahlen und die Referenzpfade an gleichartigen Punkten. Prüfen Sie, ob erwartete Objekte weiterhin verfügbar sind und veraltete Objekte nicht mehr festgehalten werden. Steigt der Ausgangswert weiterhin, untersuchen Sie die neuen Hinweise und verfolgen Sie die Referenzen weiter zurück. Geben Sie Leistungsverbesserungen nur dann an, wenn Messungen unter vergleichbaren Bedingungen sie stützen. Ziel ist es, die Änderung beim Festhalten von Objekten zu überprüfen, nicht anzunehmen, dass jede Speicherreduzierung die Anwendung schneller macht.