Keywords: Java | Tomcat | ClassLoader | Thread Management | Hot Deployment
Abstract: This paper provides an in-depth analysis of the "Illegal access: this web application instance has been stopped already" exception in Java web applications. Through a concrete case study of Spring Bean thread management, it explores the interaction between class loader lifecycle and background threads in Tomcat containers. The article first reproduces the exception scenario, then analyzes it from technical perspectives including class loader isolation mechanisms and the impact of hot deployment on runtime environments, and finally presents two solutions based on container restart and thread pool management, comparing their applicable scenarios.
Exception Scenario Reproduction and Technical Background
In Java web application development based on Tomcat, developers frequently encounter mismatches between background thread management and container lifecycle management. A typical scenario involves defining a Bean in Spring configuration that starts background threads via init-method to perform periodic tasks such as email checking. When the application is deployed or redeployed, a java.lang.IllegalStateException may occur with the error message "Illegal access: this web application instance has been stopped already".
Consider the following configuration example:
<bean id="appStarter" class="com.myapp.myClass" init-method="init" destroy-method="destroy"/>The corresponding Java class implementation:
public class myClass {
private Thread t;
public void init() {
t = new Thread() {
@Override
public void run() {
while (true) {
try {
doStuff();
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
}
}
};
t.start();
}
public void destroy() {
t.interrupt();
}
public void doStuff() {
Session session = Session.getDefaultInstance(System.getProperties(), null);
Store store = session.getStore("imap");
store.connect(hostName, userName, password);
// Additional email processing logic
}
}In-depth Analysis of Exception Causes
The core cause of this exception lies in the desynchronization between Tomcat's class loader isolation mechanism and thread lifecycle. Tomcat creates independent WebappClassLoader instances for each web application. When an application is stopped or redeployed, this ClassLoader is marked as invalid. Any attempt to load new classes through this ClassLoader at that point will throw an IllegalStateException.
In the example code, the doStuff() method internally calls Session.getStore("imap"), which triggers dynamic loading of the com.sun.mail.imap.IMAPStore class. If the web application instance has already stopped (e.g., during hot deployment) while the background thread is still running, an illegal access exception occurs.
The exception stack trace clearly illustrates this process:
java.lang.IllegalStateException
at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1273)
at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1233)
at javax.mail.Session.getService(Session.java:755)
// Subsequent stack trace omittedSolution Comparison and Implementation
Solution 1: Container Restart (Temporary Solution)
As indicated in the best answer, restarting the Tomcat server can immediately resolve the issue. This is because restarting completely clears all ClassLoader instances and thread states, ensuring the application starts from a clean environment. This approach is suitable for the following scenarios:
- Emergency failure recovery in production environments
- Confirming whether issues are cache-related in development environments
- Quick verification in testing environments
However, the restart solution has significant limitations: it cannot prevent the problem from recurring in hot deployment scenarios, and frequent restarts in production environments affect service availability.
Solution 2: Thread Pool Management and Lifecycle Synchronization (Fundamental Solution)
A more thorough solution involves using container-managed thread executor services to ensure thread lifecycle synchronization with web application lifecycle. In Java EE environments, ManagedExecutorService can be utilized:
@Resource
private ManagedExecutorService executor;
public void init() {
executor.submit(() -> {
while (!Thread.currentThread().isInterrupted()) {
try {
doStuff();
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
} catch (Exception e) {
// Exception handling logic
}
}
});
}
public void destroy() {
// ManagedExecutorService automatically manages thread termination
}The advantages of this approach include:
- Thread lifecycle managed by the container, automatically synchronized with application stop events
- Support for graceful shutdown, preventing resource leaks
- Compliance with Java EE specifications, ensuring good portability
For non-Java EE environments, consider using third-party library mechanisms like Apache Commons Pool's EvictorThread, or implement custom thread management strategies to ensure proper termination of all background threads when the application stops.
Best Practice Recommendations
Based on the above analysis, the following practical recommendations are proposed:
- Thread Creation Timing: Avoid creating infinite loop threads directly in Bean initialization methods. Consider using more controllable asynchronous mechanisms such as scheduled tasks or message queues.
- Resource Cleanup: Ensure all
destroymethods properly release resources and terminate threads, usingThread.interrupt()in conjunction with interrupt status checks. - Class Loader Isolation: Understand Tomcat's class loader hierarchy to avoid accessing classes loaded by WebappClassLoader after application stoppage.
- Monitoring and Logging: Add appropriate logging to monitor background thread status and promptly detect lifecycle mismatches.
By properly designing thread management strategies and ensuring synchronization with container lifecycle, "Illegal access" exceptions can be effectively prevented, improving the stability and maintainability of web applications.