You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Tomcat9下JNDI+HikariCP配置MS SQL JDBC驱动引发内存泄漏排查

Tomcat+JNDI+HikariCP内存泄漏警告:问题排查与修复

环境与配置情况

我在Tomcat 9.0.7环境中用JNDI配置HikariCP连接SQL Server,配置如下:

<Resource name="jdbc/mydb" auth="Container" factory="com.zaxxer.hikari.HikariJNDIFactory" type="javax.sql.DataSource" minimumIdle="5" maximumPoolSize="20" connectionTimeout="300000" dataSourceClassName="com.microsoft.sqlserver.jdbc.SQLServerDataSource" dataSource.url="jdbc:sqlserver://server:1433;databaseName=mydb" dataSource.user="fantomas" dataSource.password="somepassword" closeMethod="close" />

Tomcat安装目录./lib下的自定义库有:

ms-sql-6.4.0.jre8.jar
slf4j-api-1.7.25.jar
HikariCP-2.7.8.jar

后来我升级到HikariCP 3.1.0+Tomcat 9.0.8,问题依然存在。

问题现象

未部署任何自定义WAR包(仅标准Tomcat安装)启动时,Catalina日志出现以下内存泄漏警告:

09-May-2018 10:15:16.971 WARNING [main] org.apache.catalina.loader.WebappClassLoaderBase.clearReferencesThreads The web application [host-manager] appears to have started a thread named [mssql-jdbc-TimeoutTimer-1] but has failed to stop it. This is very likely to create a memory leak. Stack trace of thread: sun.misc.Unsafe.park(Native Method) java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:215) java.util.concurrent.SynchronousQueue$TransferStack.awaitFulfill(SynchronousQueue.java:460) java.util.concurrent.SynchronousQueue$TransferStack.transfer(SynchronousQueue.java:362) java.util.concurrent.SynchronousQueue.poll(SynchronousQueue.java:941) java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1073) java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1134) java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) java.lang.Thread.run(Thread.java:748)

问题根源

为什么会出现这个警告?

问题出在类加载器作用域与线程绑定上:

  • 你把HikariCP和SQL Server JDBC驱动放在了Tomcat的全局./lib目录,这意味着所有Web应用(包括默认的host-manager、manager)的WebappClassLoader都能加载到这些库。
  • Tomcat启动时,全局JNDI数据源初始化过程中,SQL Server驱动会创建mssql-jdbc-TimeoutTimer-1线程处理连接超时逻辑。但这个线程的上下文类加载器被绑定到了host-manager应用的WebappClassLoader(因为host-manager是第一个触发资源加载的默认应用)。
  • 当Tomcat尝试停止或重新加载host-manager时,这个线程仍在后台运行,持有WebappClassLoader的引用,导致Tomcat无法回收该类加载器,从而触发内存泄漏警告。

为什么会产生内存泄漏?

Java类加载器如果被存活线程持有引用,就无法被GC回收。这个超时线程是后台守护线程,只要Tomcat进程不终止就会一直运行,它绑定的WebappClassLoader永远无法被释放,日积月累会导致内存中堆积大量无用的类加载器和类对象,最终引发内存泄漏。

修复方案

这里提供几个可行的修复方法,你可以根据场景选择:

1. 调整库的存放位置(最推荐)

把HikariCP-*.jar和ms-sql-*.jar从Tomcat的./lib目录移到你自己的Web应用的WEB-INF/lib下。这样只有你的应用会加载这些库,默认的host-manager等应用不会接触到,也就不会触发线程绑定到WebappClassLoader的问题。

如果你的数据源是全局JNDI资源(需要所有应用共享),可以改用下面的方法:

2. 配置全局命名资源+资源链接

  • 在Tomcat的conf/server.xml的<GlobalNamingResources>节点下配置数据源:
<GlobalNamingResources>
  <Resource name="jdbc/mydb" auth="Container" factory="com.zaxxer.hikari.HikariJNDIFactory" type="javax.sql.DataSource" minimumIdle="5" maximumPoolSize="20" connectionTimeout="300000" dataSourceClassName="com.microsoft.sqlserver.jdbc.SQLServerDataSource" dataSource.url="jdbc:sqlserver://server:1433;databaseName=mydb" dataSource.user="fantomas" dataSource.password="somepassword" closeMethod="close" />
</GlobalNamingResources>
  • 然后在需要使用该数据源的Web应用的META-INF/context.xml中添加资源链接:
<Context>
  <ResourceLink name="jdbc/mydb" global="jdbc/mydb" type="javax.sql.DataSource"/>
</Context>

这样数据源由Tomcat的全局类加载器初始化,线程的上下文类加载器是全局的,不会绑定到某个Web应用的类加载器,避免内存泄漏。

3. 禁用SQL Server驱动的超时线程(谨慎使用)

可以通过设置JVM系统属性com.microsoft.sqlserver.jdbc.disableTimeoutThread=true来禁用这个超时线程。你可以在Tomcat的bin/catalina.sh(Linux)或bin/catalina.bat(Windows)中添加这个参数:

# Linux示例
CATALINA_OPTS="-Dcom.microsoft.sqlserver.jdbc.disableTimeoutThread=true $CATALINA_OPTS"

注意:这个方法会关闭驱动的连接超时检测逻辑,可能导致无效连接无法被及时清理,只有在你确认不需要该功能时才使用。

4. 升级HikariCP到最新稳定版

升级到最新稳定版的HikariCP(比如4.x系列),新版本对Tomcat的JNDI集成做了优化,能更好地处理容器关闭时的资源清理,可作为辅助手段配合其他方案使用。


内容的提问来源于stack exchange,提问作者kulatamicuda

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.27 06:36:26