2010-10-06 28 views

Respuesta

49

el Javadoc dice esto:

"Cada hilo tiene una referencia implícita a su copia de una variable de subproceso local, siempre y cuando el hilo está vivo y la instancia ThreadLocal es accesible; después de una hilo desaparece, todas sus copias de las instancias de subproceso local están sujetos a la recolección de basura (a menos que existan otras referencias a estas copias).

Si su aplicación o (si usted está hablando de hilos petición) contenedor utiliza un grupo de hilos que significa que los hilos no mueren. Si es necesario, tendría que tratar con el hilo localiza a ti mismo. La única manera limpia de hacerlo es llamar al método ThreadLocal.remove().

Hay dos razones por las que puede ser que desee para limpiar los locales de rosca para roscas en un grupo de subprocesos:

  • para evitar que la memoria (o hipotéticamente recursos) fugas, o
  • para evitar la fuga accidental de información de una solicitud a otra a través de hilo locales.

Las fugas de la memoria local de subprocesos normalmente no deberían ser un problema importante con los grupos de subprocesos delimitados, ya que es probable que eventualmente se sobrescriban los locales de subprocesos; es decir, cuando el hilo se reutiliza. Sin embargo, si comete un error al crear una nueva instancia de ThreadLocal una y otra vez (en lugar de usar una variable static para contener una instancia singleton), los valores locales de la cadena de caracteres no se sobrescribirán y se acumularán en el mapa threadlocals de cada hilo. Esto podría provocar una fuga seria.


Suponiendo que usted está hablando de los locales de rosca que se crean/utilizados durante el procesamiento de una aplicación web de una petición HTTP, a continuación, una forma de evitar las fugas locales hilo está a registrar un ServletRequestListener con su webapp ServletContext y aplicar el método requestDestroyed del oyente para limpiar los locales del hilo para el hilo actual.

Tenga en cuenta que en este contexto también debe tener en cuenta la posibilidad de que información se filtre de una solicitud a otra.

+0

gracias por la respuesta. El problema es que solo puedo eliminar el threadlocal una vez que haya terminado con la solicitud. y no tengo una manera fácil de saber cuándo realicé la solicitud. lo que hago es tener un interceptor al inicio de la solicitud que establece el threadlocal (es un estático). así que lo reinicié al inicio de cada solicitud ... – Ricardo

+1

Si el objeto threadlocal es estático, la fuga es un problema más manejable; es decir, solo se pierde (hasta) una instancia (un valor local de subproceso) por subproceso en el grupo de subprocesos. Dependiendo de cuáles sean los valores locales del hilo, no vale la pena preocuparse por esta fuga. –

+6

Esto no responde la pregunta original. En mi opinión, la pregunta es sobre cómo limpiar ThreadLocals en una aplicación web en el momento de la retirada del servicio para evitar pérdidas de memoria. Usar un ThreadLocal estático no es suficiente en este caso, ya que una aplicación redistribuida estará en otro cargador de clases. – rustyx

11

No hay manera de limpieza ThreadLocal valores, excepto desde dentro el hilo que los puso allí en el primer lugar (o cuando el hilo es basura recogida - no es el caso con los subprocesos de trabajo). Esto significa que debe tener cuidado de limpiar su ThreadLocal cuando se finaliza una solicitud de servlet (o antes de transferir AsyncContext a otro hilo en el Servlet 3), porque después de ese punto es posible que nunca tenga la oportunidad de ingresar ese hilo de trabajo específico, y por lo tanto, perderá memoria en situaciones en las que su aplicación web no se despliegue mientras el servidor no se reinicia.

Un buen lugar para hacer tal limpieza es ServletRequestListener.requestDestroyed().

Si utiliza la primavera, todo el cableado necesario ya está en su lugar, sólo tiene que poner las cosas en su ámbito de petición sin tener que preocuparse acerca de la limpieza para arriba (esto sucede automáticamente):

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

Aquí hay un código para limpiar todas las variables locales de subprocesos del subproceso actual cuando no tiene una referencia a la variable local del subproceso real. También puede generalizar a las variables locales de hilo de limpieza para otros hilos:

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 veces esta es la única manera ... o no reutilizar los hilos. – vlad

+1

Esto borra las variables globales de todos para el hilo, que me mordió en Java 6. 'java.util.concurrent.locks.ReentrantReadWriteLock' de vez en cuando lanzaba un' IllegalMonitorStateException' porque habíamos eliminado su cachedHoldCounter, por lo que intentó disminuirlo por debajo de 0 Este problema particular no ocurre en Java 8, pero quién sabe cómo reaccionan otros ThreadLocals a esto, como Spring connections o log4j MDC. – Noumenon

+0

¿Necesitamos verificar que el cargador de clases de webapp cargue el valor threadLocal? threadLocal.get(). GetClass(). GetClassLoader() == Thread.currentThread(). GetContextClassLoader() – hurelhuyag

0

me gustaría aportar mi respuesta a esta pregunta, aunque es vieja. Me había visto afectado por el mismo problema (el hilo local no se eliminaba del hilo de solicitud), e incluso me había acostumbrado a reiniciar el servidor cada vez que se quedaba sin memoria (¡lo cual es una mierda!).

En el contexto de una aplicación web java que está configurada en modo dev (porque el servidor está configurado para rebotar cada vez que detecta un cambio en el código y posiblemente también en modo de depuración), aprendí rápidamente que threadlocals puede ser increíble y en algún momento puede ser un dolor. Estaba usando una Invocación threadlocal para cada solicitud. Dentro de la Invocación. A veces también uso gson para generar mi respuesta. Me gustaría envolver la Invocación dentro de un bloque 'try' en el filtro, y destruirlo dentro de un bloque 'finally'.

Lo que observé (no tengo métricas para respaldar esto por ahora) es que si hacía cambios en varios archivos y el servidor rebotaba constantemente entre mis cambios, me impacientaba y reiniciaba el servidor (tomcat para ser precisos) del IDE. Lo más probable es que termine con una excepción de 'Falta de memoria'.

Cómo supero esto era incluir una implementación de ServletRequestListener en mi aplicación, y mi problema desapareció. Creo que lo que sucedía era que, en medio de una solicitud, si el servidor rebotaba varias veces, mis threadlocals no se estaban aclarando (incluido el gson), así que recibí esta advertencia sobre los hilos y dos o tres advertencias más adelante. el servidor se bloquee. Con ServletResponseListener cerrando explícitamente mis threadlocals, el problema gson desapareció.

Espero que esto tenga sentido y te dé una idea de cómo superar los problemas de threadlocal. Siempre ciérrelos alrededor de su punto de uso. En ServletRequestListener, pruebe cada contenedor local de thread, y si todavía tiene una referencia válida a algún objeto, destrúyalo en ese punto.

Debo señalar también que es un hábito envolver un threadlocal como una variable estática dentro de una clase. De esta forma, se le puede garantizar que al destruirlo en ServeltRequestListener, no tendrá que preocuparse por otras instancias de la misma clase dando vueltas.

0

La JVM limpiará automáticamente todos los objetos sin referencia que están dentro del objeto ThreadLocal.

Otra forma de limpiar esos objetos (por ejemplo, estos objetos podrían ser todos los objetos inseguros de hilo existentes) es ponerlos dentro de una clase Object Holder, que básicamente lo mantiene y puede anular el método finalize para limpiar el objeto que reside dentro de él. De nuevo, depende del Garbage Collector y sus políticas, cuando invocaría el método finalize.

Aquí es un ejemplo de código:

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

a leer de nuevo la documentación Javadoc cuidadosamente:

'Cada hilo tiene una referencia implícita a su copia de una variable de subproceso local, siempre y cuando el hilo es alive y la instancia de ThreadLocal es accesible; después de que un hilo desaparece, todas sus copias de instancias locales de subprocesos están sujetas a la recolección de elementos no utilizados (a menos que existan otras referencias a estas copias). '

No hay necesidad de limpiar nada, hay una condición' Y 'para que la fuga sobreviva. Así que incluso en un contenedor web donde el hilo sobreviva a la aplicación, mientras la clase de webapp esté descargada (solo la referencia de beeing en una clase estática cargada en el cargador de clase padre evitaría esto y esto no tiene nada que ver con ThreadLocal sino problemas generales con tarros compartidos con datos estáticos), entonces la segunda parte de la condición AND ya no se cumple, por lo que la copia local del subproceso es elegible para la recolección de basura.

El subproceso local no puede ser la causa de las pérdidas de memoria, siempre que la implementación cumpla con la documentación.