2010-10-06 25 views

risposta

49

JavaDoc dice questo:

"Ogni thread tiene un implicito riferimento alla sua copia di una variabile thread-local finché il filo è vivo e l'istanza ThreadLocal è accessibile; dopo thread va via, tutte le sue copie delle istanze locali del thread sono soggette alla garbage collection (a meno che non esistano altri riferimenti a queste copie)

Se l'applicazione o (se si parla di thread richiesta) utilizza un pool di thread che significa che i thread non muoiono. Se necessario, sarà necessario gestirli il thread locale. L'unico modo pulito per farlo è chiamare il metodo ThreadLocal.remove().

Ci sono due motivi che si potrebbe desiderare di ripulire i locali di thread per i thread in un pool di thread:

  • per impedire la memoria (o ipoteticamente risorsa) perdite o
  • per impedire la perdita accidentale di informazioni da una richiesta a un'altra via thread locals.

Le perdite di memoria locale del thread normalmente non dovrebbero costituire un problema importante con i pool di thread limitati, poiché eventuali locazioni di thread probabilmente verranno sovrascritte alla fine; cioè quando il filo viene riutilizzato. Tuttavia, se si commette l'errore di creare ripetutamente una nuova istanza ThreadLocal (invece di utilizzare una variabile static per contenere un'istanza singleton), i valori locali del thread non verranno sovrascritti e si accumuleranno nella mappa threadlocals di ciascun thread. Ciò potrebbe causare gravi perdite.


Supponendo che si sta parlando di gente del posto di thread che vengono creati/utilizzati durante l'elaborazione di una webapp di una richiesta HTTP, quindi un modo per evitare le fughe di notizie locali thread è di registrare un ServletRequestListener con il vostro webapp ServletContext e attuare il il metodo requestDestroyed dell'ascoltatore per pulire i thread locals per il thread corrente.

Si noti che in questo contesto è anche necessario prendere in considerazione la possibilità di informazioni perdite da una richiesta all'altra.

+0

grazie per la risposta. Il problema è che posso solo rimuovere il threadlocal una volta fatto con la richiesta. e non ho un modo semplice per sapere quando ho finito con la richiesta. il modo in cui sto facendo è che ho un intercettore all'inizio della richiesta che imposta il threadlocal (è una statica). così l'ho resettato all'inizio di ogni richiesta ... – Ricardo

+1

Se l'oggetto threadlocal è statico, la perdita è un problema più gestibile; Ad esempio, si perde solo (fino a) un'istanza (un valore locale del thread) per thread nel pool di thread. A seconda di quali sono i valori locali del thread, questa perdita potrebbe non valere la pena di preoccuparsi. –

+6

Questo non risponde alla domanda originale. A mio parere, la domanda è su come ripulire ThreadLocals in un'app Web su undeployment per evitare perdite di memoria. In questo caso, l'utilizzo di una funzione ThreadLocal statica non è sufficiente, poiché un'app ridistribuita si troverà in un altro caricatore di classi. – rustyx

11

Non v'è alcun modo per pulitura ThreadLocal valori non sia interna il filo che li batte lì in primo luogo (o quando il filo è garbage collection - non è il caso con thread di lavoro). Ciò significa che dovresti fare attenzione a ripulire il tuo ThreadLocal quando una richiesta di servlet è finita (o prima di trasferire AsyncContext su un altro thread nel Servlet 3), perché dopo quel punto potresti non avere la possibilità di inserire quel thread di lavoro specifico, e quindi, perderà memoria nelle situazioni in cui la tua app Web non viene distribuita mentre il server non viene riavviato.

Un buon posto per effettuare tale pulizia è ServletRequestListener.requestDestroyed().

Se si utilizza Primavera, tutto il cablaggio necessario è già in atto, si può semplicemente mettere roba in vostra portata richiesta senza preoccuparsi di loro pulizia (ciò accade automaticamente):

RequestContextHolder.getRequestAttributes().setAttribute("myAttr", myAttr, RequestAttributes.SCOPE_REQUEST); 
. . . 
RequestContextHolder.getRequestAttributes().getAttribute("myAttr", RequestAttributes.SCOPE_REQUEST); 
30

Ecco il codice per pulire tutte le variabili locali del thread dal thread corrente quando non si dispone di un riferimento alla variabile locale thread effettiva. È inoltre possibile generalizzare alle variabili locali thread di pulitura per altri thread:

private void cleanThreadLocals() { 
     try { 
      // Get a reference to the thread locals table of the current thread 
      Thread thread = Thread.currentThread(); 
      Field threadLocalsField = Thread.class.getDeclaredField("threadLocals"); 
      threadLocalsField.setAccessible(true); 
      Object threadLocalTable = threadLocalsField.get(thread); 

      // Get a reference to the array holding the thread local variables inside the 
      // ThreadLocalMap of the current thread 
      Class threadLocalMapClass = Class.forName("java.lang.ThreadLocal$ThreadLocalMap"); 
      Field tableField = threadLocalMapClass.getDeclaredField("table"); 
      tableField.setAccessible(true); 
      Object table = tableField.get(threadLocalTable); 

      // The key to the ThreadLocalMap is a WeakReference object. The referent field of this object 
      // is a reference to the actual ThreadLocal variable 
      Field referentField = Reference.class.getDeclaredField("referent"); 
      referentField.setAccessible(true); 

      for (int i=0; i < Array.getLength(table); i++) { 
       // Each entry in the table array of ThreadLocalMap is an Entry object 
       // representing the thread local reference and its value 
       Object entry = Array.get(table, i); 
       if (entry != null) { 
        // Get a reference to the thread local object and remove it from the table 
        ThreadLocal threadLocal = (ThreadLocal)referentField.get(entry); 
        threadLocal.remove(); 
       } 
      } 
     } catch(Exception e) { 
      // We will tolerate an exception here and just log it 
      throw new IllegalStateException(e); 
     } 
    } 
+2

a volte questo è l'unico modo .. o non riutilizzare i thread. – vlad

+1

Elimina tutte le variabili globali per il thread, che mi ha morso in Java 6. 'java.util.concurrent.locks.ReentrantReadWriteLock' occasionalmente lanciava un' IllegalMonitorStateException' perché avevamo cancellato il suo cacheHoldCounter, quindi ha provato a ridurlo sotto 0 Questo particolare problema non si verifica in Java 8, ma chissà come reagiscono altri ThreadLocals a questo, come le connessioni Spring o log4j MDC. – Noumenon

+0

Abbiamo bisogno di controllare che il valore threadLocal sia caricato dal classloader di questo webapp? threadLocal.get(). GetClass(). GetClassLoader() == Thread.currentThread(). GetContextClassLoader() – hurelhuyag

0

vorrei contribuire la mia risposta a questa domanda anche se è vecchio. Ero stato tormentato dallo stesso problema (threadlocal di gson non è stato rimosso dal thread di richiesta), ed ero persino riuscito a riavviare il server ogni volta che ha esaurito la memoria (che fa schifo !!).

Nel contesto di un'app web java impostata su dev mode (nel senso che il server è impostato per rimbalzare ogni volta che rileva un cambiamento nel codice e possibilmente anche in esecuzione in modalità di debug), ho imparato rapidamente che le filastrocche possono essere fantastiche e talvolta un dolore. Stavo usando un'invocazione threadlocal per ogni richiesta. All'interno dell'invocazione. A volte usavo anche gson per generare la mia risposta. Vorrei avvolgere l'Invocazione all'interno di un blocco 'try' nel filtro e distruggerlo all'interno di un blocco 'finally'.

Quello che ho osservato (non ho metriche per eseguire il backup di questo per ora) è che se ho apportato modifiche a diversi file e il server è stato costantemente rimbalza tra le mie modifiche, diventerei impaziente e riavviare il server (tomcat per essere precisi) dall'IDE. Molto probabilmente, avrei finito con un'eccezione "Memoria insufficiente".

In questo modo ho dovuto includere un'implementazione ServletRequestListener nella mia app e il mio problema è svanito. Penso che quello che stava succedendo è che nel bel mezzo di una richiesta, se il server dovesse rimbalzare più volte, il mio threadlocals non si stava chiarendo (incluso gson), quindi avrei ricevuto questo avviso sul threadlocals e due o tre warning più tardi, il server si arresterebbe in modo anomalo. Con il ServletResponseListener che chiude esplicitamente il mio threadlocals, il problema gson è sparito.

Spero che questo abbia un senso e ti dia un'idea di come superare i problemi di threadlocal. Chiudili sempre intorno al loro punto di utilizzo. Nel ServletRequestListener, testare ciascun wrapper threadlocal e, se ha ancora un riferimento valido a qualche oggetto, distruggerlo in quel punto.

Devo anche sottolineare che prendere l'abitudine di avvolgere un threadlocal come variabile statica all'interno di una classe. In questo modo puoi essere certo che distruggendolo nel ServeltRequestListener, non dovrai preoccuparti di altre istanze della stessa classe in giro.

0

La JVM pulisce automaticamente tutti gli oggetti senza riferimento all'interno dell'oggetto ThreadLocal.

Un altro modo per ripulire quegli oggetti (ad esempio, questi oggetti potrebbero essere tutti gli oggetti thread non sicuri che esistono) consiste nel metterli in una classe Object Holder, che in pratica lo trattiene e puoi sovrascrivere il metodo finalize per pulire l'oggetto che risiede al suo interno. Di nuovo dipende dal Garbage Collector e dalle sue politiche, quando invocerebbe il metodo finalize.

Ecco un esempio di codice:

public class MyObjectHolder { 

    private MyObject myObject; 

    public MyObjectHolder(MyObject myObj) { 
     myObject = myObj; 
    } 

    public MyObject getMyObject() { 
     return myObject; 
    } 

    protected void finalize() throws Throwable { 
     myObject.cleanItUp(); 
    } 
} 

public class SomeOtherClass { 
    static ThreadLocal<MyObjectHolder> threadLocal = new ThreadLocal<MyObjectHolder>(); 
    . 
    . 
    . 
} 
1

Rileggendo la documentazione Javadoc con attenzione:

'Ogni thread contiene un implicito riferimento alla sua copia di una variabile thread-local fino a quando il filo è vivo e l'istanza ThreadLocal è accessibile; dopo che un thread è andato via, tutte le sue copie delle istanze locali del thread sono soggette alla garbage collection (a meno che non esistano altri riferimenti a queste copie). '

Non c'è bisogno di pulire nulla, c'è una condizione' AND 'per la perdita per sopravvivere. Quindi, anche in un contenitore Web in cui il thread sopravvive all'applicazione, finché la classe webapp viene scaricata (solo il riferimento in una classe statica caricata nel caricatore della classe padre potrebbe impedire ciò e questo non ha nulla a che fare con ThreadLocal ma problemi generali con vasi condivisi con dati statici) quindi la seconda parte della condizione AND non viene più soddisfatta, quindi la copia locale del thread è idonea per la garbage collection.

Il thread locale non può essere la causa di perdite di memoria, fino a quando l'implementazione soddisfa la documentazione.

Problemi correlati