java - taskexecutor - La aplicación web parece haber iniciado un subproceso llamado[Timer-0] pero no pudo detenerlo
taskexecutor spring example (4)
Estoy usando springboot 1.5.9.RELEASE + Java 8 + tomcat 9 + Jersey + Oracle y mi aplicación ha programado el método definido de la siguiente manera:
@Configuration
@EnableScheduling
public class ScheduleConfig implements SchedulingConfigurer {
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.setScheduler(taskExecutor());
}
@Bean(destroyMethod = "shutdown")
public Executor taskExecutor() {
return Executors.newScheduledThreadPool(100);
}
}
la clase de trabajo
@Component
public class ClearCacheJob {
@Scheduled(fixedRate = 3600000, initialDelay = 10000)
public void clearErrorCodesCache() {
try {
logger.info("######## ClearCacheJob #########");
} catch (Exception e) {
logger.error("Exception in ClearCacheJob", e);
}
}
}
También tengo una clase para anular el registro del controlador oracle de la siguiente manera:
@WebListener
public class ContainerContextClosedHandler implements ServletContextListener {
private static final Logger logger = LoggerFactory.getLogger(ContainerContextClosedHandler.class);
@Override
public void contextInitialized(ServletContextEvent servletContextEvent) {
logger.info("######### contextInitialized #########");
}
@Override
public void contextDestroyed(ServletContextEvent servletContextEvent) {
logger.info("######### contextDestroyed #########");
Enumeration<Driver> drivers = DriverManager.getDrivers();
while (drivers.hasMoreElements()) {
Driver driver = drivers.nextElement();
try {
DriverManager.deregisterDriver(driver);
logger.info(String.format("deregistering jdbc driver: %s", driver));
} catch (SQLException e) {
logger.info(String.format("Error deregistering driver %s", driver), e);
}
}
}
}
pero al detener Tomcat me aparece el siguiente error:
WARNING [Thread-11] org.apache.catalina.loader.WebappClassLoaderBase.clearReferencesThreads The web application [hai]
appears to have started a thread named [Timer-0] but has failed to stop it.
This is very likely to create a memory leak. Stack trace of thread:
java.lang.Object.wait(Native Method)
java.lang.Object.wait(Unknown Source)
java.util.TimerThread.mainLoop(Unknown Source)
java.util.TimerThread.run(Unknown Source)
Por favor, avise por qué recibo este error y cómo solucionarlo, gracias.
Cambie su ScheduleConfig
para usar shutdownNow
lugar de shutdown
como método de destrucción.
@Configuration
@EnableScheduling
public class ScheduleConfig implements SchedulingConfigurer {
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.setScheduler(taskExecutor());
}
@Bean(destroyMethod = "shutdownNow")
public Executor taskExecutor() {
return Executors.newScheduledThreadPool(100);
}
}
Es difícil decir la causa raíz, pero el nombre del hilo [Timer-0] da una pista para encontrarlo. java.util.Timer
clase java.util.Timer
crea subprocesos que tienen un patrón de nombre como Timer- * como se puede ver en su código fuente.
public Timer() {
this("Timer-" + serialNumber());
}
Posiblemente las bibliotecas que están en su ruta de clase inician un subproceso del temporizador pero no lo cancelan o el código que está trabajando en este subproceso está atascado.
Puedo sugerir poner un punto de interrupción en java.util.Timer
y depurarlo para encontrar qué tareas están trabajando en él. Puede apuntar la causa raíz.
Mis conclusiones después de realizar algunas pruebas basadas en su código e investigar en línea:
No hay nada de qué preocuparse ( link ). El proceso de Tomcat se está terminando y no hay pérdidas de memoria.
Incluso si llama a algo como
AbandonedConnectionCleanupThread.shutdown()
, podría recibir la misma Advertencia ( link )Esta advertencia ocurre cuando se llama a
startup.sh
yshutdown.sh
. Cuando se ejecuta Tomcat desde Eclipse, no muestra esa Advertencia.Su método de apagado para el
Executor
es probable que se llame. Para mis pruebas, se estaba llamando incluso si no definía eldestroyMethod
dedestroyMethod
para el ejecutor.En este caso, esta advertencia no está relacionada con ningún bean de programación de Spring.
Executors.newScheduledThreadPool
devuelve un nuevoScheduledThreadPoolExecutor
, que tiene el método de destrucción y se está destruyendo, como señalé anteriormente. Puedes depurarlo y verlo por ti mismo.Sin embargo, hay un lugar en su código que llama a
new java.util.Timer
, que llama anew TimerThread()
, a su culo desde su registro, y como lo señaló @Claudio Corsi.
Para depurarlo y si está utilizando Eclipse , debe adjuntar el código fuente de su versión JDK. Abra la declaración de clase (mantenga presionado ctrl y elija abrir declaración) y haga clic en el botón "Adjuntar código fuente". Asegúrese de que ha descargado la misma versión exacta. Ni siquiera tienes que extraer la cremallera. Si está utilizando Maven, solo espere un poco que se descargará por sí mismo.
Luego, coloque un punto de interrupción en el constructor para java.util.Timer
y comience a depurar su aplicación.
Edición : después de identificar una referencia a java.util.Timer
, java.util.Timer
(como un bean, si no lo es) y llame a su método de cancel
al destruir el contexto.
Quiero compartir algunas soluciones con el análisis de la causa raíz de este problema.
Para usuarios de Oracle:
Solución # 1:
Puede eliminar su controlador Oracle de la carpeta /WEB-INF/lib
y colocarlo en la carpeta /lib
Tomcat. Puede resolver su problema.
Solución # 2:
Puedes usar hack real por hilo durmiente.
@Override
public void contextDestroyed(ServletContextEvent servletContextEvent) {
logger.info("######### contextDestroyed #########");
Enumeration<Driver> drivers = DriverManager.getDrivers();
while (drivers.hasMoreElements()) {
Driver driver = drivers.nextElement();
try {
DriverManager.deregisterDriver(driver);
logger.info(String.format("deregistering jdbc driver: %s", driver));
} catch (SQLException e) {
logger.info(String.format("Error deregistering driver %s", driver), e);
}
}
try { Thread.sleep(2000L); } catch (Exception e) {} // Use this thread sleep
}
Enlace de recursos: Solución para "Tomcat no puede detener [Subproceso de limpieza de conexión abandonada]"
Solución # 3:
Svetlin Zarev no ha dicho nada de qué preocuparse. Es el mensaje estándar de tomcat. Él ha dado el análisis de la causa raíz como a continuación:
Este problema se produce cuando una aplicación ha iniciado ScheduledExecutor (pero esto ocurrirá con cualquier otro Thread / TheadPool) y no se cerró en contextDestroyed. Así que compruebe si está cerrando sus hilos en la aplicación / parada del servidor.
Enlace de recursos: Fuga de memoria de Tomcat8
Solución # 4:
Para los usuarios de Oracle , hay varias respuestas en esta publicación: Para evitar una pérdida de memoria, el controlador JDBC no se ha registrado.
Para los usuarios de MySQL,
Solución # 5:
Análisis de la causa raíz con solución:
El subproceso de limpieza para las conexiones abandonadas en la clase NonRegisteringDriver se reformuló para tener un método de cierre estático. La memoria fue asignada pero nunca liberada. Si encontró este problema de fuga, implemente la escucha de contexto en su aplicación con la llamada
AbandonedConnectionCleanupThread.shutdown()
en el métodocontextDestroyed
.Este problema se encontró en las aplicaciones que se ejecutan bajo el servidor de aplicaciones Tomcat, pero también podría haberse aplicado a otros servidores de aplicaciones.
Por ejemplo:
@WebListener public class YourThreadsListener implements ServletContextListener { public void contextDestroyed(ServletContextEvent arg0) { try { AbandonedConnectionCleanupThread.shutdown(); } catch (InterruptedException e) { } } ... }
Tenga en cuenta que si el contenedor no admite anotaciones, agregue la descripción a web.xml:
<listener> <listener-class>user.package.YourThreadsListener</listener-class> </listener>
Enlace de recursos: https://docs.oracle.com/cd/E17952_01/connector-j-relnotes-en/news-5-1-23.html