环境描述
Tomcat10服务器,Mybatis使用POOLED模式,使用MySQL数据库。
复现步骤
- 在IDEA进行tomcat部署
- 使用show processlist查看当前连接
- 前端发送请求,后端通过mybatis创建数据库连接
- 再调show processlist(多了一条sleep状态的连接A,POOLED模式下,mybatis不会释放连接)
- 重新部署项目(非重启tomcat服务器)
- tomcat日志报错
11-Jan-2024 15:51:45.914 警告 [RMI TCP Connection(9)-127.0.0.1] org.apache.catalina.loader.WebappClassLoaderBase.clearReferencesJdbc Web应用程序 [ROOT] 注册了JDBC驱动程序 [com.mysql.cj.jdbc.Driver],但在Web应用程序停止时无法注销它。 为防止内存泄漏,JDBC驱动程序已被强制取消注册。
11-Jan-2024 15:51:45.914 警告 [RMI TCP Connection(9)-127.0.0.1] org.apache.catalina.loader.WebappClassLoaderBase.clearReferencesJdbc Web应用程序 [ROOT] 注册了JDBC驱动程序 [org.apache.ibatis.datasource.unpooled.UnpooledDataSource.DriverProxy],但在Web应用程序停止时无法注销它。 为防止内存泄漏,JDBC驱动程序已被强制取消注册。
11-Jan-2024 15:51:45.915 警告 [RMI TCP Connection(9)-127.0.0.1] org.apache.catalina.loader.WebappClassLoaderBase.clearReferencesThreads Web应用程序[ROOT]似乎启动了一个名为[mysql-cj-abandoned-connection-cleanup]的线程,但未能停止它。这很可能会造成内存泄漏。线程的堆栈跟踪:[
java.base@17.0.8/java.lang.Object.wait(Native Method)
java.base@17.0.8/java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:155)
com.mysql.cj.jdbc.AbandonedConnectionCleanupThread.run(AbandonedConnectionCleanupThread.java:93)
java.base@17.0.8/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
java.base@17.0.8/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
java.base@17.0.8/java.lang.Thread.run(Thread.java:833)]
11-Jan-2024 15:51:50.562 信息 [mysql-cj-abandoned-connection-cleanup] org.apache.catalina.loader.WebappClassLoaderBase.checkStateForResourceLoading 非法访问:此Web应用程序实例已停止。无法加载[]。为了调试以及终止导致非法访问的线程,将抛出以下堆栈跟踪。
java.lang.IllegalStateException: 非法访问:此Web应用程序实例已停止。无法加载[]。为了调试以及终止导致非法访问的线程,将抛出以下堆栈跟踪。
at org.apache.catalina.loader.WebappClassLoaderBase.checkStateForResourceLoading(WebappClassLoaderBase.java:1373)
at org.apache.catalina.loader.WebappClassLoaderBase.getResource(WebappClassLoaderBase.java:971)
at com.mysql.cj.jdbc.AbandonedConnectionCleanupThread.checkThreadContextClassLoader(AbandonedConnectionCleanupThread.java:125)
at com.mysql.cj.jdbc.AbandonedConnectionCleanupThread.run(AbandonedConnectionCleanupThread.java:92)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
at java.base/java.lang.Thread.run(Thread.java:833)
- 前端再发请求
- 再调show processlist(又多了一条连接B,A并未断开)
正常情况下,多次访问会优先使用连接池中已有的空闲链接,而不是新建。所以A连接已经处于无法正常使用的状态。
若重复以上步骤(重新部署,访问数据库,重新部署,访问数据库...),最终将消耗大量连接资源。(虽然只会在开发时才会有这种反复部署的需求吧)
解决方法
解决tomcat日志报错
tomcat日志报错实质与本问题无关,但可以利用其解决方法。
任意位置新建MyServletContextListener类:
package priv.liaohaolong.webapps.telephonebook.utils;
import com.mysql.cj.jdbc.AbandonedConnectionCleanupThread;
import jakarta.servlet.ServletContextEvent;
import jakarta.servlet.ServletContextListener;
import jakarta.servlet.annotation.WebListener;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Enumeration;
@WebListener
public class MyServletContextListener implements ServletContextListener {
@Override
public void contextDestroyed(ServletContextEvent sce) {
// 如果Web应用拥有多个数据库的连接,可以一并关闭
Enumeration<Driver> drivers = DriverManager.getDrivers();
Driver driver = null;
while (drivers.hasMoreElements()) {
try {
driver = drivers.nextElement();
DriverManager.deregisterDriver(driver);
} catch (SQLException ignored) {
}
}
AbandonedConnectionCleanupThread.uncheckedShutdown();
}
}
tomcat日志报错解决方法来自:CSDN博客
解决连接未正确释放
原理:从sqlSessionFactory -> mybatis配置 -> evironment配置中获取dataSource实例,如果使用的是POOLED模式,则为PooledDataSource实例,其中提供了forceCloseAll来强制断开连接。
DataSource dataSource = sqlSessionFactory.getConfiguration().getEnvironment().getDataSource();
if (dataSource instanceof PooledDataSource pooledDataSource)
pooledDataSource.forceCloseAll();
在你的sqlSessionFactory管理类中,将上述三行包装成一个方法
最后在MyServletContextListener#contextDestroyed()的开头中调用即可。