Keywords: Spring Boot | Thread Pool | Memory Leak
Abstract: This paper examines the warning "Timer-0 thread not stopped" in Spring Boot 1.5.9 applications deployed on Tomcat 9. Based on Q&A data, the issue is traced to the shutdown method of ScheduledThreadPoolExecutor failing to terminate threads promptly. The optimal solution is changing the destroyMethod from shutdown to shutdownNow, ensuring forceful thread termination during application shutdown. The article also discusses Oracle driver deregistration, memory leak risks, and debugging techniques, providing comprehensive technical guidance for developers.
In web applications built with Spring Boot 1.5.9.RELEASE and deployed on Tomcat 9, developers often encounter a warning: "The web application appears to have started a thread named [Timer-0] but has failed to stop it. This is very likely to create a memory leak." This warning indicates that background threads are not properly terminated during application shutdown, potentially leading to memory leaks. Based on Q&A data, this article delves into the root cause and presents effective solutions.
Problem Context and Error Log
The developer uses Spring Boot 1.5.9.RELEASE, Java 8, Tomcat 9, Jersey, and an Oracle database. The application configures scheduled tasks via @EnableScheduling and ScheduledTaskRegistrar. The task class ClearCacheJob employs the @Scheduled annotation to clear cache hourly. Additionally, a ServletContextListener is included to deregister the Oracle JDBC driver upon context destruction.
When stopping the Tomcat server, the console outputs the following warning:
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)
The stack trace shows that the Timer-0 thread originates from java.util.TimerThread, indicating that Timer-related threads are not being terminated.
Root Cause Analysis
According to the best answer (Answer 2) in the Q&A data, the core issue lies in the thread pool destruction method configured in the ScheduleConfig class. The original code uses @Bean(destroyMethod = "shutdown"), which invokes the ScheduledThreadPoolExecutor.shutdown() method. This method stops accepting new tasks but allows submitted tasks to complete, potentially leaving threads active during application shutdown and triggering Tomcat's warning.
Other answers provide supplementary insights: Answer 1 notes that placing the Oracle driver in Tomcat's /lib folder instead of /WEB-INF/lib may interfere with thread management; Answer 3 mentions that java.util.Timer might be implicitly created in the code, requiring manual cancellation of its threads; Answer 4 suggests checking Maven build integrity, though it is less relevant to this issue.
Solution: Using the shutdownNow Method
The optimal solution is to modify the ScheduleConfig class by changing the destroyMethod from "shutdown" to "shutdownNow". The revised code is as follows:
@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);
}
}
The shutdownNow method attempts to stop all active tasks immediately and returns a list of tasks that were awaiting execution. This ensures that the thread pool is forcefully terminated during application context destruction, preventing residual Timer-0 threads. This method is effective for ScheduledThreadPoolExecutor, as Executors.newScheduledThreadPool returns an instance of this executor.
Additional Considerations and Optimization Tips
Beyond the core solution, developers should consider the following aspects to optimize their applications:
- Driver Management: Ensure the Oracle JDBC driver is deployed only in
WEB-INF/libto avoid conflicts with Tomcat's classloader. Deregistering drivers in thecontextDestroyedmethod is a good practice, but adding thread sleep (e.g.,Thread.sleep(2000L)) may be necessary to ensure cleanup completion, as mentioned in Answer 1. - Memory Leak Prevention: Tomcat's warning aims to highlight potential memory leaks. Although Answer 3 suggests that this warning is harmless after Tomcat process termination, best practices involve proactive thread lifecycle management. For databases like MySQL, calling
AbandonedConnectionCleanupThread.shutdown()(referenced in Answer 1) can help, but note it may not apply universally. - Debugging and Verification: Use an IDE (e.g., Eclipse) to debug the creation points of
java.util.Timer, ensuring no implicit Timer threads exist. Verify thatshutdownNowis invoked incontextDestroyedthrough logs or breakpoints.
Conclusion
The issue of Timer-0 threads not stopping in Spring Boot applications primarily stems from the delayed termination characteristic of ScheduledThreadPoolExecutor.shutdown(). By switching to the shutdownNow method, the thread pool can be forcefully terminated, eliminating Tomcat warnings. Combined with driver management, memory leak prevention, and debugging measures, developers can build more stable web applications. This solution is based on Spring Boot 1.5.9 and Tomcat 9 environments, but the principles apply to similar configurations.