Fall und Analyse
Problemhintergrund
In Tomcat befindet sich der folgende Code im WebApp, wodurch WebappClassLoader ausgeleckt wird und nicht recycelt werden kann.
öffentliche Klasse mycounter {private int count = 0; public void Increment () {count ++; } public int getCount () {return count; }} public class mythreadlocal erweitert ThreadLocal <MyCounter> {} Public Class LeakingServlet erweitert HttpServlet {private statische MyThreadlocal myThreadLocal = new myThreadlocal (); Protected void dodget (httpServletRequest -Anforderung, httpServletResponse -Antwort) löst ServletException aus, ioException {mycounter counter = mythreadlocal.get (); if (counter == null) {counter = new MyCounter (); MyThreadlocal.set (Zähler); } response.getWriter (). println ("Der aktuelle Thread serviert dieses Servlet" + counter.getCount () + "Zeiten"); counter.increment (); }} Im obigen Code erfolgt WebappClassLoader , solange LeakingServlet einst aufgerufen wird und der Thread nicht gestoppt wird. Jedes Mal, wenn Sie die Anwendung reload , haben Sie eine zusätzliche WebappClassLoader -Instanz, die schließlich zu PermGen OutOfMemoryException führt.
Das Problem lösen
Denken wir nun darüber nach: Warum verursacht ThreadLocal -Unterklasse über Speicherlecks?
WebAppClassloader
Zunächst müssen wir herausfinden, was zum Teufel WebappClassLoader ist?
Für Webanwendungen, die in Java EE -Containern ausgeführt werden, unterscheidet sich die Implementierung von Klassenladern von der von allgemeinen Java -Anwendungen. Auch unterschiedliche Webcontainer werden unterschiedlich implementiert. In Apache Tomcat verfügt jede Webanwendung über eine entsprechende Klassenladerinstanz. Dieser Klassenloader verwendet auch den Proxy -Modus. Der Unterschied besteht darin, dass er zuerst versucht, eine bestimmte Klasse zu laden, und dann Proxy in den übergeordneten Klassenlader, wenn er nicht gefunden werden kann. Dies ist das Gegenteil der Reihenfolge der allgemeinen Klassenlader. Dies ist eine empfohlene Praxis in der Java -Servlet -Spezifikation, und der Zweck ist es, die eigenen Klassen der Webanwendung höher zu gestalten als die vom Webcontainer bereitgestellten Klassen. Eine Ausnahme zu diesem Proxy -Muster besteht darin, dass die Klassen in der Java -Kernbibliothek nicht im Rahmen der Suche liegen. Dies soll auch die Art Sicherheit der Java -Kernbibliothek gewährleisten.
Mit anderen Worten, WebappClassLoader ist ein benutzerdefinierter Klassenload für Tomcat zum Laden von WebApps. Der Klassenloader jeder WebApp ist unterschiedlich. Dies soll die von verschiedenen Anwendungen geladenen Klassen isolieren.
Was haben die Funktionen von WebappClassLoader mit Speicherlecks zu tun? Es ist noch nicht sichtbar, aber es ist eine sehr wichtige Funktion, die unsere Aufmerksamkeit verdient: Jedes WebApp verfügt über einen eigenen WebappClassLoader , der sich vom Java -Core -Class -Loader unterscheidet.
Wir wissen, dass WebappClassLoader daran liegen muss, dass es stark von anderen Objekten verwiesen wird, sodass wir versuchen können, ihr Referenzbeziehungsdiagramm zu zeichnen. usw! Was genau funktioniert ein Klassenlader? Warum ist es gezwungen zu zitieren?
Klassenlebenszyklus und Klassenlader
Um das obige Problem zu lösen, müssen wir die Beziehung zwischen dem Lebenszyklus der Klasse und dem Klassenlader untersuchen.
Die Hauptsache in Bezug auf unseren Fall ist die Deinstallation der Klassen:
Nachdem die Klasse verwendet wurde, wird die Klasse deinstalliert, wenn die folgende Situation erfüllt ist:
1. Alle Fälle dieser Klasse wurden recycelt, dh keine Fälle dieser Klasse existieren im Java -Haufen.
2. ClassLoader , der diese Klasse lädt, wurde recycelt.
3. Das java.lang.Class -Objekt, das dieser Klasse entspricht, wird nirgendwo verwiesen, und es gibt keine Methode, um durch Überlegungen überall auf die Klasse zugreifen zu können.
Wenn alle oben genannten drei Bedingungen erfüllt sind, deinstalliert der JVM die Klasse während der Müllsammlung im Methodenbereich. Der Deinstallationsprozess der Klasse soll die Klasseninformationen im Methodenbereich löschen, und der gesamte Lebenszyklus der Java -Klasse endet.
Klassen, die vom Klassenlader geladen werden, der mit der Java Virtual Machine geliefert wird, werden während des Lebenszyklus der virtuellen Maschine niemals deinstalliert. Zu den Klassenladern, die mit Java -virtuellen Maschinen geliefert werden, gehören Root -Klassenlader, Erweiterungsklassenlader und Systemklassenlader. Die java -virtuelle Maschine selbst verweist immer auf diese Klassenlader, und diese Klassenlader verweisen immer auf die Klassenobjekte der von ihnen geladenen Klasse, sodass diese Klassenobjekte immer zugänglich sind.
Klassen, die von benutzerdefinierten Klassenladern geladen werden, können entladen werden.
Beachten Sie den obigen Satz. Wenn WebappClassLoader aussieht, bedeutet dies, dass die Klassen, die sie geladen hat, nicht deinstalliert werden können, was erklärt, warum der obige Code PermGen OutOfMemoryException verursacht.
Schauen Sie sich das Bild unten am wichtigsten Punkt an
Wir können feststellen, dass das Klassenladerobjekt bidirektional dem Klassenobjekt zugeordnet ist, das es lädt. Dies bedeutet, dass das Klassenobjekt möglicherweise der Schuldige des erzwungenen Verweiss auf WebappClassLoader ist und dazu führt, dass es leckt.
Zitat -Beziehungsdiagramm
Nachdem wir die Beziehung zwischen dem Klassenlader und dem Lebenszyklus einer Klasse verstanden haben, können wir ein Referenzbeziehungsdiagramm zeichnen. ( LeakingServlet.class und myThreadLocal in der Abbildung sind in der Referenzzeichnung nicht streng, hauptsächlich ausdrücken, dass myThreadLocal eine Klassenvariable ist)
Im Folgenden analysieren wir die Ursache des WebappClassLoader -Lecks basierend auf der obigen Abbildung.
1. LeakingServlet hält static MyThreadLocal , was zum Lebenszyklus von myThreadLocal führt, solange der Lebenszyklus LeakingServlet -Klasse ist. Dies bedeutet, dass myThreadLocal nicht recycelt wird und schwache Referenzen nutzlos sind, sodass der aktuelle Thread die starke Referenz des Zählers durch die Schutzmaßnahmen von ThreadLocalMap nicht löschen kann.
2. Starke Referenzkette: thread -> threadLocalMap -> counter -> MyCounter.class -> WebappClassLocader , wodurch WebappClassLoader ausgelöst wird.
Zusammenfassen
Speicherlecks sind schwer zu erkennen und werden häufig durch viele Gründe verursacht. ThreadLocal ist aufgrund seines Lebenszyklus zu einem häufigen Besucher von Speicherlecks geworden, und eine kleine Nachlässigkeit führt zu einer Katastrophe. Dieser Artikel ist nur eine Analyse eines bestimmten Falls. Wenn Sie dies verwenden können, um es auf andere Fälle anzuwenden, wäre dies ausgezeichnet. Ich hoffe, dieser Artikel wird für alle hilfreich sein.