termino - ¿Cómo identificar y eliminar Threads/ThreadLocals iniciados desde nuestra aplicación web en Java?
multihilos java (7)
Intentaría encontrar qué biblioteca causa estos ThreadLocal, posiblemente ejecutando la aplicación web en un depurador y deteniendo la creación de ThreadLocal. Luego puede ver si olvidó limpiar detrás de alguna biblioteca o si una biblioteca tiene errores o no está hecha para el uso de la aplicación web. Tal vez publicar sus hallazgos aquí.
Al limpiar subprocesos en un escucha de contexto, una vez comprobé que el contextClassLoader del subproceso era el mismo que el subproceso que ejecuta el escucha, para evitar que se enreden los subprocesos de otras aplicaciones.
Espero que esto ayude.
Cada vez que detengo o vuelvo a desplegar la aplicación web, veo muchos errores similares a,
msg=The web application [] created a ThreadLocal with key of type [] (value []) and
a value of type [] (value []) but failed to remove it when the web application was
stopped. Threads are going to be renewed over time to try and avoid probable memory leak
No estoy creando ninguna ThreadLocals en mi aplicación, sino que hago referencia a muchas bibliotecas que pueden estar creando estas ThreadLocals. Actualmente estamos utilizando Tomcat 7. Ya he pasado por otras preguntas similares [Pérdida de memoria al redistribuir la aplicación en Tomcat o ¿Qué son estas advertencias en catalina.out?] Pero todas ellas solo sugieren que esta es la función de Tomcat para advertirle sobre ThreadLocals no se eliminan. No veo ninguna respuesta para eliminar ThreadLocals. También veo algunos errores con respecto al hilo que no se detiene también,
msg=The web application [] appears to have started a thread named [] but has
failed to stop it. This is very likely to create a memory leak.
Estos se están registrando como ERROR en el sistema de registro central de nuestra empresa y, por lo tanto, aumentan el número de errores de nuestra aplicación. Esto ciertamente no se ve bien cuando verificamos el rendimiento de nuestra aplicación. Probé las implementaciones de estas dos fuentes [Matar subprocesos y Código de muestra de este subproceso] , pero no parece funcionar. Elimina hilos / threadlocals no creados por nuestra aplicación. Lo que necesito es eliminar solo los hilos / threadlocals iniciados por nuestra aplicación web. ¿Hay alguna manera de eliminarlos en el método contextDestroyed () de ServletContextListener? A continuación se encuentra mi clase actual ServletContextListener,
public class CustomServletContextListener implements ServletContextListener {
private List<String> threadsAtStartup;
@Override
public void contextInitialized(ServletContextEvent sce) {
retrieveThreadsOnStartup();
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
// Now deregister JDBC drivers in this context''s ClassLoader:
// Get the webapp''s ClassLoader
ClassLoader cl = Thread.currentThread().getContextClassLoader();
// Loop through all drivers
Enumeration<Driver> drivers = DriverManager.getDrivers();
while (drivers.hasMoreElements()) {
Driver driver = drivers.nextElement();
if (driver.getClass().getClassLoader() == cl) {
// This driver was registered by the webapp''s ClassLoader, so deregister it:
try {
System.out.println("Deregistering JDBC driver {}: " + driver);
DriverManager.deregisterDriver(driver);
} catch (SQLException ex) {
System.out.println("Error deregistering JDBC driver {}: " + driver + "/nException: " + ex);
}
} else {
// driver was not registered by the webapp''s ClassLoader and may be in use elsewhere
System.out.println("Not deregistering JDBC driver {} as it does not belong to this webapp''s ClassLoader: " + driver);
}
}
//Threads
ThreadGroup threadGroup = Thread.currentThread().getThreadGroup();
threadGroup = Thread.currentThread().getThreadGroup();
Thread[] threads;
try {
threads = retrieveCurrentActiveThreads(threadGroup);
} catch (NoSuchFieldException e) {
System.out.println("Could not retrieve initial Threads list. The application may be unstable on shutting down " + e.getMessage());
return;
} catch (IllegalAccessException e) {
System.out.println("Could not retrieve initial Threads list. The application may be unstable on shutting down " + e.getMessage());
return;
}
int toBeKilledCount = 0;
int totalThreadCount = 0;
int killedTLCount = 0;
int totalTLCount = 0;
int killedITLCount = 0;
int totalITLCount = 0;
for (; totalThreadCount < threads.length; totalThreadCount++) {
Thread thread = threads[totalThreadCount];
if(thread != null) {
String threadName = thread.getName();
boolean shouldThisThreadBeKilled;
shouldThisThreadBeKilled = isThisThreadToBeKilled(Thread.currentThread(), thread);
if (shouldThisThreadBeKilled) {
//ThreadLocal
try {
removeThreadLocals("threadLocals", thread);
removeThreadLocals("inheritableThreadLocals", thread);
} catch (Exception e) {
System.out.println("/tError accessing threadLocals field of ''" + threadName + "'': " + e.getMessage());
}
//Stop thread
thread.interrupt();
thread = null;
toBeKilledCount++;
}
}
}
}
private void retrieveThreadsOnStartup() {
final Thread[] threads;
final ThreadGroup threadGroup = Thread.currentThread().getThreadGroup();
try {
threads = retrieveCurrentActiveThreads(threadGroup);
} catch (NoSuchFieldException e) {
System.out.println("Could not retrieve initial Threads list. The application may be unstable on shutting down " + e);
threadsAtStartup = new ArrayList<String>();
return;
} catch (IllegalAccessException e) {
System.out.println("Could not retrieve initial Threads list. The application may be unstable on shutting down " + e);
threadsAtStartup = new ArrayList<String>();
return;
}
threadsAtStartup = new ArrayList<String>(threads.length);
for (int i = 0; i < threads.length; i++) {
final Thread thread;
try {
thread = threads[i];
if (null != thread) {
threadsAtStartup.add(thread.getName());
}
} catch (RuntimeException e) {
System.out.println("An error occured on initial Thread statement: " + e);
}
}
}
private Thread[] retrieveCurrentActiveThreads(ThreadGroup threadGroup) throws NoSuchFieldException, IllegalAccessException {
final Thread[] threads;
final Field privateThreadsField;
privateThreadsField = ThreadGroup.class.getDeclaredField("childrenThreads");
privateThreadsField.setAccessible(true);
threads = (Thread[]) privateThreadsField.get(threadGroup);
return threads;
}
private void removeThreadLocals(String fieldName, Thread thread) {
Field threadLocalsField = Thread.class.getDeclaredField(fieldName);
threadLocalsField.setAccessible(true);
Object threadLocalMap = threadLocalsField.get(thread);
Field tableField = threadLocalMap.getClass().getDeclaredField("table");
tableField.setAccessible(true);
Object table = tableField.get(threadLocalMap);
int count = 0;
for (int i = 0, length = Array.getLength(table); i < length; ++i) {
Object entry = Array.get(table, i);
if (entry != null) {
totalTLCount++;
Object threadLocal = ((WeakReference)entry).get();
if (threadLocal != null) {
Array.set(table, i, null);
killedTLCount++;
}
}
}
}
private Boolean isThisThreadToBeKilled(Thread currentThread, Thread testThread) {
boolean toBeKilled;
String currentThreadName = currentThread.getName();
String testThreadName = testThread.getName();
System.out.println("currentThreadName: " + currentThreadName + ", testThreadName: " + testThreadName);
return !threadsAtStartup.contains(testThreadName) // this thread was not already running at startup
&& !testThreadName.equalsIgnoreCase(currentThreadName); // this is not the currently running thread
}
}
Actualización: Todavía no puedo resolver esto. ¿Alguna ayuda? Nadie se topó con estos?
La solución depende de la biblioteca que creó estos Threads / ThreadLocal-s. Básicamente, debe llamar al código de limpieza de la biblioteca desde su método CustomServletContextListener.contextDestroyed ().
Así que encuentra qué es la biblioteca y cómo cerrarla correctamente.
No hay solución para arreglar todas las filtraciones de threadlocal de una sola vez. Normalmente, las bibliotecas de terceros que utilizan variables Threadlocal tienen algún tipo de llamada de API de limpieza que se puede utilizar para borrar sus variables de subproceso locales.
Debe verificar todas las filtraciones de threads locales y encontrar la forma correcta de desecharlas en la biblioteca correspondiente. Puedes hacer esto en tu CustomServletContextListener
ejemplos:
log4j ( javadoc ):
LogManager.shutdown()
controlador jdbc: ( javadoc ):
DriverManager.deregisterDriver(driver);
nota: También revise las nuevas versiones de sus bibliotecas de terceros para verificar las correcciones de zorros relacionadas con las fugas de memoria (y / o las fugas locales de hilos).
Puedes probar este código para eliminar todos los ThreadLocal
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);
}
}
Si eliminamos los objetos del subproceso local de todos los subprocesos al detener el contenedor, solo intentamos resolver el problema de los mensajes de error que se muestran por contenedor. Más bien, el objetivo debe ser evitar las fugas de memoria que pueden ocurrir durante el período de tiempo cuando el contenedor no se detiene / reinicia. Por lo tanto, lo ideal es que los subprocesos de ThreadPool se reutilicen para atender diferentes solicitudes y luego, después de que se envíe la respuesta, no debería haber ninguna razón para mantener la memoria manteniendo los objetos en el subproceso local porque ese subproceso se puede usar para atender la siguiente solicitud (totalmente diferente) del cliente . Una de las sugerencias es eliminar cualquier objeto del subproceso local configurando el Filtro que se ejecuta inmediatamente antes de que se envíe la respuesta desde el servidor.
Si usas ThreadLocal en tu código, podrías reemplazar ThreadLocal con ImrpovedThreadLocal que hice y no tendrás una pérdida de memoria al detener / volver a desplegar. Puede usar ese threadLocal de la misma manera sin tener ninguna contención de hilo.
tratar
Runtime.getRuntime().addShutdownHook(webapp.new ShutdownHook());
En tu shudownhook, limpia los objetos.