-
-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Description
Bug: Memory Leak in Web Containers due to Static ExecutorService
The org.web3j.utils.Async class utilizes a static instance of ExecutorService which relies solely on a JVM Shutdown Hook for termination. This design prevents the threads from stopping when a Web Application is undeployed, causing ClassLoader leaks.
Steps To Reproduce
- Create a Java Web Application (WAR) that depends on the Web3j library.
- Deploy the application to a Servlet Container (e.g., Apache Tomcat, Jetty).
- Execute code that triggers the initialization of
org.web3j.utils.Async(this starts the static cached thread pool). - Reload or Undeploy the web application via the container's management interface (without restarting the JVM).
- Take a Heap Dump and analyze it (e.g., using Eclipse MAT).
Expected behavior
The ExecutorService threads should terminate (or provide a mechanism to be terminated) when the application context is destroyed, releasing references to the WebAppClassLoader and allowing it to be garbage collected.
Actual behavior
The threads created by the static executor remain alive because the JVM Shutdown Hook (Runtime.getRuntime().addShutdownHook) is not triggered during application redeployment (only during JVM termination).
These "Not Terminated" threads hold strong references to the WebAppClassLoader, preventing it from being garbage collected. Repeated redeployments lead to java.lang.OutOfMemoryError: Metaspace (or PermGen).
Environment
Describe the environment in which the issue occurs
- Web3j version: [填入你的 Web3j 版本,例如 4.8.7]
- Java or Android version: [填入你的 Java 版本,例如 Java 8 / 11]
- Operating System: [填入你的操作系统,例如 Windows / Linux]
- Container: [填入你的容器,例如 Apache Tomcat 9.0]
Additional context
Root Cause Analysis:
The issue lies in org.web3j.utils.Async:
private static final ExecutorService executor = Executors.newCachedThreadPool();
static {
Runtime.getRuntime().addShutdownHook(new Thread(() -> shutdown(executor)));
}
Since the executor is private and static, there is no way for a developer to manually shut it down using a ServletContextListener.
Suggested Fix:
Please provide a public static method (e.g., Async.shutdown()) to allow manual resource cleanup, or avoid using a static thread pool that assumes it owns the JVM lifecycle.