Der Titel könnte ein bisschen stark sein, aber lassen Sie mich erklären, wie ich verstehe, was passiert. Ich vermute, das passierte mit Tomcat (und die angegebene Nachricht kommt von Tomcat), aber ich bin mir nicht mehr sicher.
TL; DR Am Ende gibt es eine Zusammenfassung, warum ich behaupte, dass es die Fehler der Webserver sind.
Ich könnte mich irren (aber ohne die Möglichkeit, falsch zu liegen, gäbe es keinen Grund zu fragen):
ThreadLocal
ThreadLocal
bezieht sich auf ein Objekt aus der Bibliothek ClassLoader
Der Webserver
Wenn ich es richtig verstanden habe, werden die alten "dreckigen" Threads nach einer erneuten Bereitstellung weiter verwendet. Ihre ThreadLocal
s beziehen sich auf die alten Klassen, die sich auf ihre ClassLoader
beziehen, die sich auf die gesamte alte Klassenhierarchie beziehen. So bleibt eine Menge Material im PermGen
Raum, was im Laufe der Zeit zu einem OutOfMemoryError
führt. Ist das soweit richtig?
Ich nehme zwei Dinge an:
So kostet eine vollständige Erneuerung des Threadpools bei jeder erneuten Bereitstellung einen Bruchteil einer Millisekunde einige Male pro Stunde, d. h. es gibt einen Zeitaufwand von 0.0001 * 12/3600 * 100%
, d. h. 0.000033%
.
Aber anstatt diesen kleinen Overhead zu akzeptieren, gibt es unzählige Probleme . Ist meine Rechnung falsch oder was übersehe ich?
Als Warnung erhalten wir die Nachricht
Die Webanwendung ... hat ein ThreadLocal mit dem Schlüssel vom Typ ... und einem Wert vom Typ ... erstellt, konnte es aber nicht entfernen, als die Webanwendung beendet wurde.
sollte besser als
angegeben werdenDer Webserver ... verwendet einen Thread-Pool, konnte ihn jedoch nach dem Beenden (oder erneuten Bereitstellen) einer Anwendung nicht erneuern.
Oder liege ich falsch? Der Zeitaufwand ist vernachlässigbar, selbst wenn alle Threads von Zeit zu Zeit neu erstellt werden. Aber ihre ThreadLocal
s zu löschen, bevor sie den Anwendungen zur Verfügung gestellt werden, würde ausreichen und noch schneller sein.
Es gibt einige echte Probleme (vor kurzem dieser ) und der Benutzer kann nichts dagegen tun. Die Bibliotheksschreiber können manchmal und manchmal nicht. IMHO die Webserver könnten es ziemlich leicht lösen. Das Ding passiert und hat eine Ursache. Also mache ich die einzige Party verantwortlich, die etwas dagegen tun könnte.
Der Titel dieser Frage ist provokativer als korrekt, aber er hat seinen Zweck. Und so auch die Antwort von raphw. Diese verknüpfte Frage hat eine weitere offene Prämie.
Ich denke, die Webserver könnten es wie folgt lösen:
LastCleanupTimestamp
in ThreadLocal
(für neue Threads ist dies die Erstellungszeit) delta
, z. B. 1 Stunde) ThreadLocal
s und setze eine neue LastCleanupTimestamp
Dies würde sicherstellen, dass kein solches Leck länger als delta
plus der Dauer der längsten Anfrage plus der Thread-Bearbeitungszeit existiert. Die Kosten würden sich wie folgt zusammensetzen:
ThreadLocal
(d. h. einige Nanosekunden) pro Anfrage ThreadLocal
s werden reflektiv bereinigt (d. h. einige Nanosekunden einmal pro delta
pro Thread) DateFormat
-Instanz, wenn jemand immer noch solch eine schreckliche Sache benutzt.) Es kann ausgeschaltet werden, indem einfach die threshold-Einstellung gesetzt wird, wenn in letzter Zeit keine App-Bereitstellung aufgehoben oder neu implementiert wurde.
TL; DR Es sind keine Webserver, die Speicherlecks verursachen. Du bist es.
Lassen Sie mich zuerst das Problem genauer angeben: ThreadLocal
Variablen beziehen sich oft auf eine Instanz von Class
, die von einem ClassLoader
geladen wurde, das ausschließlich von der Anwendung eines Containers verwendet werden sollte. Wenn diese Anwendung nicht bereitgestellt wird, wird die ThreadLocal
-Referenz verwaist. Da jede Instanz eine Referenz auf ihre Class
behält und jede Class
eine Referenz auf ihre ClassLoader
behält und jede ClassLoader
einen Verweis auf alle Klassen behält, die sie jemals geladen hat, kann der gesamte Klassenbaum der nicht-deployten Anwendung nicht Müll wird gesammelt und die JVM-Instanz leidet unter einem Speicherverlust .
Wenn Sie sich dieses Problem ansehen, können Sie Folgendes optimieren:
Die meisten Entwickler von Webanwendungen würden argumentieren, dass der erste wichtiger ist, da der zweite durch das Schreiben von gutem Code erreicht werden kann. Und was passiert, wenn eine erneute Bereitstellung gleichzeitig mit lang andauernden Anfragen erfolgt? Sie können den alten Thread-Pool nicht herunterfahren, da dies laufende Anforderungen unterbrechen würde. (Es gibt kein global definiertes Maximum für die Dauer eines Request-Zyklus.) Am Ende benötigen Sie dafür ein recht komplexes Protokoll, das seine eigenen Probleme mit sich bringt.
Das ThreadLocal
induzierte Leck kann jedoch vermieden werden, indem immer geschrieben wird:
Auf diese Weise wird Ihr Thread immer sauber . (Nebenbei bemerkt, das ist fast so, als würde man globale Variablen erstellen: Es ist fast immer eine schreckliche Idee. Es gibt einige Web-Frameworks wie zum Beispiel Wicket, die eine Menge davon nutzen. Web-Frameworks wie Dies ist schrecklich zu verwenden, wenn Sie Dinge gleichzeitig ausführen müssen und für andere Benutzer sehr unintuitiv sind.Es gibt einen Trend weg vom typischen Java Ein Thread pro Anfrage -Modell, wie es mit Play und Netty demonstriert wurde. Bleib nicht mit diesem Anti-Muster stecken, benutze ThreadLocal
sparsam! Es ist fast immer ein Zeichen für schlechtes Design.)
Sie sollten außerdem beachten, dass Speicherverluste, die von ThreadLocal
verursacht werden, nicht immer erkannt werden. Speicherlecks werden erkannt, indem der Worker-Thread-Pool des Webservers nach ThreadLocal
Variablen durchsucht wird. Wenn eine ThreadLocal
Variable gefunden wurde, zeigt die Class
der Variablen ClassLoader
. Wenn dieses ClassLoader
oder eines seiner Eltern das der Webanwendung ist, die gerade nicht implementiert wurde, kann der Webserver einen Speicherverlust annehmen.
Stellen Sie sich jedoch vor, Sie hätten ein großes Array von String
s in einer Variable ThreadLocal
gespeichert. Wie kann der Webserver annehmen, dass dieses Array zu Ihrer Anwendung gehört? Das String.class
wurde natürlich mit der bootstrap ClassLoader
-Instanz der JVM geladen und kann keiner bestimmten Webanwendung zugeordnet werden. Durch das Entfernen des Arrays kann der Webserver einige andere Anwendungen unterbrechen, die im selben Container ausgeführt werden. Wenn der Web-Server nicht entfernt wird, kann eine große Speichermenge verloren gehen. (Diesmal ist es kein ClassLoader
und seine Class
es, die durchgesickert sind. Abhängig von der Größe des Arrays könnte dieses Leck jedoch sogar noch schlimmer sein.)
Und es wird schlimmer. Stellen Sie sich vor, dass Sie in Ihrer Variablen ArrayList
ein ThreadLocal
gespeichert haben. Das ArrayList
ist Teil der Java-Standardbibliothek und wird daher mit dem System ClassLoader
geladen. Auch hier kann nicht gesagt werden, dass die Instanz zu einer bestimmten Webanwendung gehört. Diesmal werden jedoch Ihre ClassLoader
und all ihre Classes
sowie alle Instanzen solcher Klassen, die im lokalen Thread ArrayList
gespeichert sind, undicht. Diesmal kann der Webserver nicht sicher feststellen, dass ein Speicherleck aufgetreten ist, wenn festgestellt wird, dass die ClassLoader
keine Speicherbereinigung war, da die Speicherbereinigung nur einer JVM empfohlen werden kann (via System#gc()
), aber nicht erzwungen wird.
Das Erneuern des Threadpools ist nicht so günstig, wie Sie vielleicht annehmen.
Eine Webanwendung kann nicht einfach alle Threads in einem Thread-Pool entfernen, wenn eine Anwendung nicht bereitgestellt wird. Was, wenn Sie einige Werte in diesen Threads gespeichert haben? Wenn eine Webanwendung einen Thread recycelt, sollte sie (ich bin mir nicht sicher, ob alle Webserver dies tun) alle nicht-leaking Thread-lokalen Variablen finden und sie im ersetzten Thread
registrieren. Die Zahlen, die Sie über die Effizienz angegeben haben, würden daher nicht länger gelten.
Gleichzeitig muss der Webserver eine Logik implementieren, die das Ersetzen aller Threadpools Thread
s verwaltet, was nicht für Ihre vorgeschlagene Zeitberechnung funktioniert.(Möglicherweise müssen Sie mit lang andauernden Anforderungen fertig werden - denken Sie daran, einen FTP-Server in einem Servlet-Container auszuführen - so dass diese Thread-Pool-Übergangslogik möglicherweise für eine lange Zeit aktiv ist.)
Außerdem ist ThreadLocal
nicht die einzige Möglichkeit, ein Speicherleck in einem Servlet-Container zu erstellen.
Einen Abschalthaken zu setzen ist ein anderes Beispiel. (Und es ist leider eine gewöhnliche. Hier sollten Sie den heruntergefahrenen Hook manuell entfernen, wenn Ihre Anwendung nicht bereitgestellt wird. Dieses Problem würde nicht durch Verwerfen von Threads gelöst werden.) Herunterfahren-Hooks sind außerdem Instanzen von benutzerdefinierten Unterklassen von Thread
das wurden immer vom Klassenlader einer Anwendung geladen.
Im Allgemeinen kann jede Anwendung, die einen Verweis auf ein Objekt behält, das von einem Kindklassenladeprogramm geladen wurde, einen Speicherverlust verursachen. (Dies ist in der Regel über Thread#getContextClassLoader()
möglich.) Letztendlich ist es die Verantwortung des Entwicklers, keine Speicherverluste zu verursachen, selbst in Java-Anwendungen, in denen viele Entwickler die automatische Speicherbereinigung falsch interpretieren, da keine Speicherlecks sind . (Denken Sie an Jochua Blochs berühmtes Stack-Implementierungsbeispiel . )
Nach dieser allgemeinen Aussage möchte ich Tomcat's Speicherleckschutz kommentieren:
Tomcat verspricht nicht Ihnen, alle Speicherlecks zu erkennen , sondern deckt bestimmte Arten von solchen ab Lecks, wie sie in ihrem Wiki aufgeführt sind. Was Tomcat eigentlich macht:
Jeder Thread in der JVM wird untersucht und die internen Strukturen der Thread - und ThreadLocal - Klassen werden überprüft, ob entweder die ThreadLocal-Instanz oder der an sie gebundene Wert wurden von der geladen WebAppClassLoader der Anwendung wird beendet.
Einige Versionen von Tomcat versuchen sogar, das Leck zu kompensieren:
Tomcat 6.0.24 bis 6.0.26 modifizieren interne Strukturen des JDK (ThreadLocalMap) um den Verweis auf die ThreadLocal-Instanz zu entfernen, aber das ist unsicher (siehe # 48895), so dass es optional wurde und Standardmäßig deaktiviert von 6.0.27. Starten von mit Tomcat 7.0.6, dem Die Gewinde des Pools werden erneuert, so dass das Leck sicher fixiert ist.
Sie müssen Tomcat jedoch ordnungsgemäß dafür konfigurieren. Der Wiki-Eintrag zu seinem Speicherleckschutz warnt sogar, wie man andere Anwendungen durchbrechen kann, wenn TimerThread
s beteiligt sind oder wie man Speicherlecks beim Starten von eigenen Thread
s oder ThreadPoolExecutor
s oder bei gemeinsamen Abhängigkeiten für mehrere Web-Anwendungen.
Die von Tomcat angebotenen Aufräumarbeiten sind ein letzter Ausweg ! Es ist nichts, was Sie in Ihrem Produktionscode haben möchten.
Zusammengefasst : Es ist nicht Tomcat, das ein Speicherleck erzeugt, es ist Ihr Code. Einige Versionen von Tomcat versuchen, solche Lecks zu kompensieren, die nachweisbar sind, wenn sie dafür konfiguriert sind. Es liegt jedoch in Ihrer Verantwortung, Speicherlecks zu vermeiden. Sie sollten die Warnungen von Tomcat als Aufforderung sehen, Ihren Code zu reparieren, anstatt Tomcat neu zu konfigurieren, um Ihr Chaos zu beseitigen. Wenn Tomcat in Ihrer Anwendung Speicherlecks erkennt, sind möglicherweise sogar mehr vorhanden. Nehmen Sie also einen Heap- und Thread-Dump aus Ihrer Anwendung, und finden Sie heraus, wo sich Ihr Code befindet leckt .
Tags und Links java memory-leaks webserver thread-local tomcat