ThreadLocal Memory Leak

Da ich schon häufiger auf Memory Leaks gestoßen bin, die durch ThreadLocal Variablen auftreten, möchte ich dieses Phänomen genauer beleuchten.

ThreadLocal Variablen werden in Java benutzt, um Variablen an einen Thread zu binden – jeder Thread bekommt eine eigene, unabhängige Instanz dieser Variable zugewiesen. Normalerweise werden diese Variablen genutzt, um Status Informationen innerhalb eines Threads zu halten – z.B. die Benutzerkennung oder einen Mandanten.

Der Lebenszyklus einer ThreadLocal Instanz ist eng gekoppelt an den Lebenszyklus des entsprechenden Threads. Wird der Thread beendet und durch den GarbageCollector entfernt, sind auch die damit assoziierten ThreadLocal Variablen Kandidaten für den GarbageCollector.

Die Memory Probleme treten dann auf, wenn ThreadLocal Variablen innerhalb eines Application Servers verwendet werden. Application Server verwalten Thread in eigenen Thread Pools, um Ressourcen einzusparen und die Performance zu optimieren (Beispiel der Einstellungen für Tomcat 6 Http Conncector). Wird z.B. ein HttpServletRequest an die ServletEngine des Application Servers gesendet, wird für die Abarbeitung des Servlets ein freier, so genannten Worker-Thread aus dem entsprechenden Servlet ThreadPool entnommen. Dieser Thread arbeitet dann die Programmlogik des Servlets ab. Wird im Servlet oder der aus dem Servlet aufgerufenen Java Klassen eine ThreadLocal Variable genutzt, werden diese mit dem aktuellen Thread assoziiert. Nachdem das Servlet vom Thread abgearbeitet und die Response geschrieben wurde, wird der Thread aber nicht beendet und vom Garbage Collector aufgeräumt, sondern wieder in den ThreadPool zurückgegeben, so dass mit dem selben Thread einen neue Anfrage abgearbeitet werden kann. Die ThreadLocal Variablen bleiben also erhalten.

Je nach Größe des ThreadPools (in produktiven Systemen können das mehrere Hundert Threads sein) und der Größe der Objekte in der ThreadLocal Variable, kann das zu Problemen führen. Sind beispielsweise 200 Threads im ThreadPool aktiv und die ThreadLocal Varibale ist 5MB groß, belegen diese Variablen im schlimmsten Fall bereits 1GB Heap Speicher. Dies führt schnell zu erhöhter GC Aktivität und je nach Heap Größe zu einem Abbruch der JVM.

In einem konkreten Fall musste ein Server täglich neu gestartet werden, weil es zu einem OutOfMemoryError gekommen ist. Um das Prolem zu analysieren wurde zur Laufzeit ein Heapdump erzeugt. (siehe dazu den Blog Eintrag zur Erzeugung von Heapdumps) Die Analyse des Dumps mit Eclipse MAT hat ziemlich schnell gezeigt, dass es sich um ein ThreadLocal Problem handelt.

Der obige Screenshot zeigt den Dominator Tree des Dumps. Markiert sind 6 Threads die jeweils ca. 14MB Speicher referenzieren.

Der nächste Screenshot zeigt die Details eines Threads. Der hohe Speicherverbrauch ist auf eine ThreadLocal Variable zurückzuführen, die eine DOMParser inkl. geparstem Dokument referenziert – insgesamt mehr als 14MB groß. Da MAT auch die Inhalte der Objekte anzeigt, konnte an Hand des XML Inhalts identifiziert werden, dass die Dokumente zu einem WebService Aufruf gehören. Die Zugriffsklassen wurden mit JBoss WS generiert. Eine Suche in Google führte schnell zu einem Bug in der eingesetzten jbossws Version 1.2.0, der in der Version 1.2.1 gefixt wurde: “DOMUtils doesn’t clear thread locals”.

Das Beispiel zeigt, dass man vorsichtig sein muss, wenn man ThreadLocal Variablen innerhalb eines ApplicationServer verwendet (vor allem wenn man Application Server Hersteller ist). Es ist wichtig dafür zu sorgen, dass der Inhalt der Variablen gelöscht wird, bevor der Thread wieder in den ThreadPool zurückgestellt wird. Die Daten sind auch fachlich gefährlich, wenn Sie nicht gelöscht werden, da nicht vorhergesagt werden kann, welche Anfrage der Thread als nächstes bearbeitet.

In einigen Projekten in denen wir ThreadLocal Variablen nutzen, haben wir einen ServletFilter geschrieben, der dafür sorgt, dass die Daten bereinigt werden.

Mirko Novakovic

themenverwandte Beiträge:

5 Antworten auf ThreadLocal Memory Leak

  1. Sudhir Mongia sagt:

    Good One!! I used ThreadLocal but I was not aware about this fact. good to know.

  2. Olivier sagt:

    Correctly developed there is absolutely no risk in using thread local :

      protected ThreadLocal myThreadLocal = new ThreadLocal();
     
      public void doFilter (ServletRequest req, ServletResponse res, chain) throws IOException, ServletException {
        try {
     
          // Set ThreadLocal (eg. to store heavy computation result done only once per request)
          myThreadLocal.set(computeSomeLargeObject());
     
          chain.doFilter(req, res);
     
        } finally {
          // Important : cleanup ThreaLocal to prevent memory leak
          userIsSmartTL.remove();
        }
      }

    But the new Memory Leak Protection feature introduced in Tomcat 6.0.25 can come handy if you use badly written software :)

    http://wiki.apache.org/tomcat/MemoryLeakProtection

  3. Arun sagt:

    I ran into the same problem in a webservice. We had to clear off the threadlocal variables prior to dispatching the response in a interceptor / using AOP.

    I was surprised when I ran into this issue to see that there is a loop hole like this. It will be better if application servers can take care of it so that Threadlocal can be used effectively in Java EE applications.

  4. Ashish Kumar sagt:

    Very easy to understand and informative. Thanks.

  5. Bubak sagt:

    I wander if thread pool actually saves that much performance. Maybe just disable it.

Hinterlasse eine Antwort

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind markiert *

*

Du kannst folgende HTML-Tags benutzen: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre lang="" line="" escaped="">

© 2010 codecentric