解决 registered the JDBC driver [com.mysql.cj.jdbc.Driver] but failed to unregister it when the web application was stopped. To prevent a memory leak, the JDBC Driver has been forcibly unregistered./appears to have started a thread named [mysql-cj-abandoned-connection-cleanup] but has failed to stop it.
Springboot在tomcat Undeploying,或者容器中关闭时老是提示如下警告,虽然不会影响程序的运行,但是还是想去掉这些不必要的异常:
xxx 警告 [http-nio-8080-exec-56] org.apache.catalina.loader.WebappClassLoaderBase.clearReferencesJdbc The web application [gasbj] registered the JDBC driver [com.alibaba.druid.proxy.DruidDriver] but failed to unregister it when the web application was stopped. To prevent a memory leak, the JDBC Driver has been forcibly unregistered. xxx 警告 [http-nio-8080-exec-56] org.apache.catalina.loader.WebappClassLoaderBase.clearReferencesJdbc The web application [gasbj] registered the JDBC driver [com.mysql.cj.jdbc.Driver] but failed to unregister it when the web application was stopped. To prevent a memory leak, the JDBC Driver has been forcibly unregistered. xxx 警告 [http-nio-8080-exec-56] org.apache.catalina.loader.WebappClassLoaderBase.clearReferencesThreads The web application [gasbj] appears to have started a thread named [mysql-cj-abandoned-connection-cleanup] 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.ref.ReferenceQueue.remove(Unknown Source) com.mysql.cj.jdbc.AbandonedConnectionCleanupThread.run(AbandonedConnectionCleanupThread.java:85) java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source) java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source) java.lang.Thread.run(Unknown Source) xxx 信息 [mysql-cj-abandoned-connection-cleanup] org.apache.catalina.loader.WebappClassLoaderBase.checkStateForResourceLoading Illegal access: this web application instance has been stopped already. Could not load []. The following stack trace is thrown for debugging purposes as well as to attempt to terminate the thread which caused the illegal access. java.lang.IllegalStateException: Illegal access: this web application instance has been stopped already. Could not load []. The following stack trace is thrown for debugging purposes as well as to attempt to terminate the thread which caused the illegal access. at org.apache.catalina.loader.WebappClassLoaderBase.checkStateForResourceLoading(WebappClassLoaderBase.java:1348) at org.apache.catalina.loader.WebappClassLoaderBase.getResource(WebappClassLoaderBase.java:1007) at com.mysql.cj.jdbc.AbandonedConnectionCleanupThread.checkThreadContextClassLoader(AbandonedConnectionCleanupThread.java:117) at com.mysql.cj.jdbc.AbandonedConnectionCleanupThread.run(AbandonedConnectionCleanupThread.java:84) at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source) at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source) at java.lang.Thread.run(Unknown Source)
发生这种异常的原因有两个:
一是Tomcat在 6.0.24 后加入了一个 memory leak detection 检查机制,当我们程序中引入了JDBC 4.0 驱动时会自动注册到Tomcat容器中,但是却没有提供自动销毁机制。
二是我们Mysql驱动中自带的AbandonedConnectionCleanupThread类,会在我们加载jdbc驱动时启动一个newSingleThreadExecutor的线程池,这个也不会自动释放。
解决方式如下(注册一个ServletContextListener,注意使用@ServletComponentScan来扫描):
package com.iwhere.gasbj.config.listener; import lombok.extern.slf4j.Slf4j; import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; import javax.servlet.annotation.WebListener; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.sql.Driver; import java.sql.DriverManager; import java.sql.SQLException; import java.util.Enumeration; @WebListener @Slf4j public class JdbcUnregisterListener implements ServletContextListener { @Override public void contextDestroyed(ServletContextEvent sce) { try { log.info("Calling MySQL AbandonedConnectionCleanupThread checkedShutdown"); // Or com.mysql.jdbc.AbandonedConnectionCleanupThread Class cls = Class.forName("com.mysql.cj.jdbc.AbandonedConnectionCleanupThread"); Method method = cls.getMethod("checkedShutdown"); method.invoke(null); } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { log.error("Cannot call MySQL AbandonedConnectionCleanupThread.checkedShutdown!", e); } // 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 { log.info("Deregistering JDBC driver {}", driver); DriverManager.deregisterDriver(driver); } catch (SQLException ex) { log.error("Error deregistering JDBC driver {}", driver, ex); } } else { // driver was not registered by the webapp's ClassLoader and may be in use elsewhere log.trace("Not deregistering JDBC driver {} as it does not belong to this webapp's ClassLoader", driver); } } } }