Ich habe eine Webanwendung, die in Tomcat 7.0.70 implementiert ist. Ich habe die folgende Situation simuliert:
ERGEBNISSE
Thread-Dump
Auf dem folgenden Bildschirm können Sie sehen, dass nach dem Klicken auf "erneut implementieren" alle Threads (die dieser Webanwendung zugeordnet waren) mit Ausnahme des Threads "http-apr-8081-exec-10" beendet wurden. Da ich das Tomcat-Attribut "renewThreadsWhenStoppingContext == true" gesetzt habe, können Sie sehen, dass dieser Thread ("http-apr-8081-exec-10") nach einiger Zeit beendet wurde und ein neuer Thread (http-apr-8081-exec-11) beendet wurde ) wurde stattdessen erstellt. Ich hatte also nicht damit gerechnet, die alte WCL nach der Erstellung von Heap Dump 3 zu haben, da es keine alten Threads oder Objekte gibt.
Heapd Dump 1
Auf den folgenden zwei Bildschirmen können Sie sehen, dass es beim Ausführen der Anwendung nur eine WCL gab (der Parameter "started" = true). . Der Thread "http-apr-8081-exec-10" hatte die contextClassLoader = URLClassLoader (weil es sich im Tomcat-Pool befand). Ich spreche nur über diesen Thread, da Sie sehen können, dass dieser Thread meine zukünftige HTTP-Anforderung verarbeitet.
HTTP-Anfrage senden
Nun sende ich die HTTP-Anfrage und erhalte in meinem Code Informationen über den aktuellen Thread. Sie können sehen, dass meine Anfrage vom Thread "http-apr-8081-exec-10" verarbeitet wird.
дек 23, 2016 9:28:16 AM c.c.c.f.s.r.ReportGenerationServiceImpl INFO: request has been handled in
thread = http-apr-8081-exec-10, its contextClassLoader = WebappClassLoader
context: /hdi
delegate: false
repositories:
/WEB-INF/classes/
----------> Parent Classloader: [email protected]
Dann klicke ich auf "Web-Anwendung erneut bereitstellen" und erhalte folgende Meldung in der Konsole.
дек 23, 2016 9:28:27 AM org.Apache.catalina.loader.WebappClassLoaderBase clearReferencesThreads
SEVERE: The web application [/hdi] appears to have started a thread named [http-apr-8081-exec-10] but has failed to stop it. This is very likely to create a memory leak.
Heapd Dump 2
In den folgenden Bildschirmen sehen Sie, dass es zwei Instanzen gibt: WebAppClassLoader. Einer von ihnen (Nummer # 1) ist alt (sein Attribut "started" = false). Und die WCL # 2 wurde erstellt, nachdem die Anwendung erneut implementiert wurde (ihr Attribut "started" = true). Und der Thread, den wir überprüfen, hat contextClassLoader = "org.Apache.catalina.loader.WebappClassLoader". Warum? Ich hatte erwartet, contextClassLoader = "Java.net.URLClassLoader" zu sehen (schließlich wird ein Thread nach Beendigung seiner Arbeit an den Pool Des Tomcat zurückgegeben und sein Attribut "contextClassLoader" wird auf einen beliebigen Basisklassenlader gesetzt).
Heapd-Dump 3
Sie sehen, dass es keinen Thread "http-apr-8081-exec-10" gibt, aber es gibt den Thread "http-apr-8081-exec-11" und es hat contextClassLoader = "WebappClassLoader" ( Warum nicht URLClassLoader?).
Am Ende haben wir folgendes: Es gibt den Thread "http-apr-8081-exec-11", der den Verweis auf den WebappClassLoader # 1 hat. Und wenn ich "Nearest GC Root" auf der WCL # 1 mache, sehe ich den Hinweis auf den Thread 11.
Fragen.
Wie kann ich Tomcat zwingen, den alten Wert contextClassLoader (URLClassLoader) zurückzugeben, nachdem der Thread seine Arbeit beendet hat?
Wie kann ich sicherstellen, dass Tomcat während der Thread-Erneuerung keinen alten Wert "contextClassLoader" kopiert?
Wissen Sie vielleicht einen anderen Weg, um mein Problem zu lösen?
Tomcat ist in Produktionsumgebungen normalerweise keine gute Option. Ich habe Tomcat für ein paar Produktionsanwendungen verwendet, und ich habe festgestellt, dass selbst wenn die Größe des Heapspeichers und andere Konfigurationen ordnungsgemäß eingerichtet sind - und jedes Mal, wenn Sie Ihre Anwendung neu laden, der Speicherverbrauch immer höher wird. Bis Sie den Tomcat-Dienst nicht neu starten, wird der Speicher nicht vollständig freigegeben. Wir haben alle diese Experimente getestet, wie zum Beispiel Protokolle gelöscht, alle Apps erneut bereitgestellt und Tomcat regelmäßig einmal im Monat oder in einer Woche zu den am wenigsten beanspruchten Stunden neu gestartet. Aber am Ende muss ich sagen, dass wir unsere Produktionsumgebungen auf Glassfish und WebSphere verlagert haben.
Ich hoffe, Sie hätten diese Seiten bereits durchlaufen:
Speicherverlust in einer Java-Webanwendung
https://developers.redhat.com/blog/2014/08/14/find-fix-memory-leaks-Java-application/
http://www.tomcatexpert.com/blog/2010/04/06/tomcats-new-memory-leak-prevention-and-detection
Wenn Ihre Webanwendungen nicht eng mit Tomcat gekoppelt sind, können Sie einen anderen Webcontainer verwenden. Jetzt setzen wir den Glassfish sogar auf Entwicklungsmaschinen und in der Produktion ein. An dem Tag, an dem wir diese Entscheidung treffen, haben wir viel Zeit gespart. Glassfish und andere Server benötigen zwar mehr Zeit, da sie nicht so leicht sind wie der Tomcat, aber das Leben nach dem Leben ist etwas einfacher.
Aus meiner Erfahrung mit diesem Problem: Was Tomcat daran hinderte, ältere Klassenlader ordnungsgemäß zu GC-fähig zu machen, waren einige ThreadLocal
s, die von einigen Frameworks erstellt wurden (und nicht ordnungsgemäß gehandhabt wurden).
Ähnliches, was hier erklärt wird: ThreadLocal & Memory Leak
Ich habe versucht, diese ThreadLocal
s richtig zu finalisieren, und mein Leck hat das VIEL reduziert. Es war immer noch undicht, aber ich konnte zehnmal mehr Umschichtungen durchführen als zuvor.
Ich würde definitiv Ihre Speicherabbilder auf Objekte überprüfen, die irgendwie mit ThreadLocal
s verbunden sein könnten (sie sind sehr üblich, insbesondere wenn Sie etwas zur Steuerung von Transaktionen oder etwas verwenden, das Thread-isoliert ist).
Ich hoffe, es hilft!
Tomcat ist in Produktionsumgebungen normalerweise keine gute Option. Ich habe Tomcat für ein paar Produktionsanwendungen verwendet, und ich habe festgestellt, dass selbst wenn die Größe des Heapspeichers und andere Konfigurationen ordnungsgemäß eingerichtet sind - und jedes Mal, wenn Sie Ihre Anwendung neu laden, der Speicherverbrauch immer höher wird. Bis Sie den Tomcat-Dienst nicht neu starten, wird der Speicher nicht vollständig freigegeben. Wir haben alle diese Experimente getestet, wie zum Beispiel Protokolle gelöscht, alle Apps erneut bereitgestellt und Tomcat regelmäßig einmal im Monat oder in einer Woche zu den am wenigsten beanspruchten Stunden neu gestartet. Aber am Ende muss ich sagen, dass wir unsere Produktionsumgebungen auf Glassfish und WebSphere verlagert haben.
Ein Speicherleck bei Tomcat's Neuverwendung ist ein sehr altes Problem. Der einzige Weg, um das Problem zu lösen, ist, Tomcat neu zu starten, anstatt die Anwendung erneut zu implementieren. Wenn Sie über mehrere Apps verfügen, müssen Sie mehrere Tomcat-Dienste an verschiedenen Ports ausführen und sich mit nginx verbinden.
Suchen Sie nach ThreadLocal-Verwendungen, die verhindern, dass Ihr ClassLoader Müll sammelt. Entfernen Sie entweder Verweise auf Ihre Klassen in ThreadLocal-Werten oder verwenden Sie https://github.com/codesinthedark/ImprovedThreadLocal anstelle von ThreadLocal
Wir haben Hunderte von Tomcat-Instanzen, die in mehreren Umgebungen (auch in der Produktion) ausgeführt werden. Die einzige vernünftige Lösung, die wir für dieses Problem gefunden haben, besteht darin, jeden Tomcat zu einer bestimmten Zeit zu stoppen und erneut zu starten daily (in der Nacht).
Wir haben viele Tricks ausprobiert, aber dies ist die dauerhafte Lösung für unsere Verfügbarkeitsanforderungen.