Der Text stammt aus einer beliebten Diskussion auf der StackOverflow-Q&A-Website: Wie schreibe ich einen Code in Java, der Speicherlecks verursacht?
F: Ich habe gerade an einem Interview teilgenommen und der Interviewer hat mich gefragt, wie man Java-Code schreibt, der Speicherverluste verursacht. Ich habe keine Ahnung von dieser Frage, die so peinlich ist.
A1: Durch die folgenden Schritte kann es leicht zu Speicherlecks kommen (Programmcode kann nicht auf bestimmte Objekte zugreifen, diese werden aber dennoch im Speicher gespeichert):
Die Anwendung erstellt einen Thread mit langer Laufzeit (bzw Verwenden Sie einen Thread-Pool, wodurch Speicher schneller verloren geht.
Der Thread lädt eine Klasse über einen Klassenlader (kann angepasst werden).
Diese Klasse weist einen großen Speicherblock zu (z. B. neues Byte [1000000]), speichert eine starke Referenz in einer statischen Variablen und speichert dann ihre eigene Referenz in ThreadLocal. Das Zuweisen von zusätzlichem Speicher (neues Byte[1000000]) ist optional (Klasseninstanzlecks sind ausreichend), aber dadurch wird der Speicherverlust schneller.
Der Thread bereinigt die benutzerdefinierte Klasse oder den Klassenlader, der die Klasse lädt.
Wiederholen Sie die oben genannten Schritte.
Da es keine Verweise auf die Klasse und den Klassenlader gibt, kann nicht auf den Speicher in ThreadLocal zugegriffen werden. ThreadLocal enthält einen Verweis auf das Objekt sowie einen Verweis auf die Klasse und ihren Klassenlader. Der Klassenlader enthält alle Verweise auf die von ihm geladenen Klassen, sodass der GC den in ThreadLocal gespeicherten Speicher nicht zurückfordern kann. In vielen JVM-Implementierungen werden Java-Klassen und Klassenlader direkt dem Permgen-Bereich zugewiesen, ohne dass eine GC durchgeführt wird, was zu schwerwiegenderen Speicherlecks führt.
Eine Variante dieses Leckmusters besteht darin, dass es leicht zu Speicherlecks kommt, wenn Sie Anwendungen und Anwendungscontainer (z. B. Tomcat), die ThreadLocal in irgendeiner Form verwenden, häufig erneut bereitstellen (da der Anwendungscontainer ThreadLocal wie zuvor verwendet). (Bei jedem erneuten Bereitstellen der Anwendung wird ein neuer Klassenlader verwendet.)
A2:
Statisches Variablenreferenzobjekt
class MemorableClass { static final ArrayList list = new ArrayList(100); }
String.intern() einer langen Zeichenfolge aufrufen
String str=readString(); // read lengthy string any source db,textbox/jsp etc.. // This will place the string in memory pool from which you cant remove str.intern();
Nicht geschlossener geöffneter Stream (Datei, Netzwerk usw.)
try { BufferedReader br = new BufferedReader(new FileReader(inputFile)); ... ... } catch (Exception e) { e.printStacktrace(); }
Ungeschlossene Verbindung
try { Connection conn = ConnectionFactory.getConnection(); ... ... } catch (Exception e) { e.printStacktrace(); }
GC nicht erreichbarer Bereich der JVM
Zum Beispiel durch native Methoden zugewiesener Speicher.
Webanwendungsobjekte im Anwendungsbereich, die Anwendung wurde nicht neu gestartet oder nicht explizit entfernt
getServletContext().setAttribute("SOME_MAP", map);
web Anwendungsobjekte im Sitzungsbereich, die nicht ungültig gemacht oder nicht explizit entfernt wurden
session.setAttribute("SOME_MAP", map);
Falsche oder unangemessene JVM-Optionen
Zum Beispiel , IBM JDKs noclassgc verhindert die Garbage Collection nutzloser Klassen
A3: Wenn HashSet hashCode() oder equal() nicht korrekt implementiert (oder nicht implementiert), führt dies dazu, dass weiterhin „Kopien“ hinzugefügt werden die Sammlung. Wenn die Sammlung die Elemente, die sie ignorieren sollte, nicht ignoriert, kann ihre Größe nur weiter wachsen und die Elemente können nicht gelöscht werden.
Wenn Sie falsche Schlüssel-Wert-Paare generieren möchten, können Sie wie folgt vorgehen:
class BadKey { // no hashCode or equals(); public final String key; public BadKey(String key) { this.key = key; } } Map map = System.getProperties(); map.put(new BadKey("key"), "value"); // Memory leak even if your threads die.
A4: Zusätzlich zu vergessenen Listenern und statischen Referenzen ist der Schlüssel in der Hashmap falsch /is Typische Speicherverlustszenarien wie Änderungen oder Thread-Blockierungen, die den Lebenszyklus nicht beenden können. Hier sind einige weniger offensichtliche Speicherverlustsituationen in Java, die hauptsächlich mit Threads zusammenhängen.
Runtime.addShutdownHook wird nach runtime.addShutdownHook nicht entfernt. Aufgrund des Fehlers der ThreadGroup-Klasse für nicht gestartete Threads wird es möglicherweise nicht recycelt, was zu einem Speicherverlust in der ThreadGroup führt.
Den Thread erstellen, aber nicht starten, gleiche Situation wie oben
Erstellen Sie einen Thread, der ContextClassLoader und AccessControlContext erbt, verwenden Sie ThreadGroup und InheritedThreadLocal, alle diese Referenzen sind potenzielle Lecks und alle sind geladene Klassen durch den Klassenlader und alle statischen Referenzen und so weiter. Dies hat ganz offensichtliche Auswirkungen auf die ThreadFactory-Schnittstelle als wichtigen Bestandteil des gesamten j.u.c.Executor-Frameworks (java.util.concurrent), und viele Entwickler haben die potenziellen Gefahren nicht bemerkt. Und viele Bibliotheken starten Threads entsprechend den Anforderungen.
ThreadLocal-Caching ist in vielen Fällen keine gute Praxis. Es gibt viele Implementierungen von einfachem Caching, die auf ThreadLocal basieren, aber der ContextClassLoader wird auslaufen, wenn der Thread weiterhin außerhalb seines erwarteten Lebenszyklus ausgeführt wird. Verwenden Sie ThreadLocal-Caching nicht, es sei denn, dies ist wirklich erforderlich.
ThreadGroup.destroy() wird aufgerufen, wenn ThreadGroup selbst keine Threads, aber dennoch untergeordnete Thread-Gruppen hat. Wenn ein Speicherverlust auftritt, kann die Thread-Gruppe nicht aus ihrer übergeordneten Thread-Gruppe entfernt werden und untergeordnete Thread-Gruppen können nicht aufgelistet werden.
Bei Verwendung von WeakHashMap bezieht sich der Wert direkt (indirekt) auf den Schlüssel, was schwierig zu finden ist. Dies gilt auch, wenn eine Klasse, die Weak/SoftReference erbt, möglicherweise einen starken Verweis auf das geschützte Objekt enthält.
Verwenden Sie die java.net.URL des http(s)-Protokolls, um Ressourcen herunterzuladen. KeepAliveCache erstellt einen neuen Thread in der System-ThreadGroup, wodurch der Speicher des Kontextklassenladers des aktuellen Threads verloren geht. Der Thread wird bei der ersten Anfrage erstellt, wenn es keinen überlebenden Thread gibt, sodass ein Leck sehr wahrscheinlich ist. (Dies wurde in Java 7 behoben, sodass der Thread-Erstellungscode den Kontextklassenlader ordnungsgemäß entfernt.)
Verwenden Sie InflaterInputStream, um neue java.util.zip.Inflater() im Konstruktor (z. B. PNGImageDecoder) zu übergeben, ohne end() des Inflaters aufzurufen. Just new ist sehr sicher, aber wenn Sie die Klasse selbst als Konstruktorparameter erstellen und close() des Streams aufrufen und den Inflater nicht schließen können, kann es zu Speicherlecks kommen. Dabei handelt es sich nicht wirklich um einen Speicherverlust, da er vom Finalizer freigegeben wird. Dies verbraucht jedoch viel nativen Speicher, was dazu führt, dass oom_killer von Linux den Prozess abbricht. Die Lektion, die wir daraus lernen, lautet also: Geben Sie native Ressourcen so früh wie möglich frei.
Das Gleiche gilt für java.util.zip.Deflater, was noch schwerwiegender ist. Das Gute ist, dass Deflater selten verwendet wird. Wenn Sie selbst einen Deflater oder Inflater erstellen, denken Sie daran, dass Sie end() aufrufen müssen.