首页 > 解决方案 > 如何解决“Web 应用程序 [myapp] 似乎已经启动了一个名为 [HikariPool-113 管家] 的线程但未能阻止它”?

问题描述

我正在运行一个 Spring Boot 项目,我有一个调用 SQL Server 数据库的方法,它连接良好并返回结果。

但是,当我开始重新部署更新时,这会出现在 Tomcat 日志中:

==> catalina.2021-04-25.log <==
25-Apr-2021 09:05:45.307 WARNING [Catalina-utility-2] org.apache.catalina.loader.WebappClassLoaderBase.clearReferencesJdbc The web application [ciamdev] registered the JDBC driver [com.microsoft.sqlserver.jdbc.SQLServerDriver] but failed to unregister it when the web application was stopped. To prevent a memory leak, the JDBC Driver has been forcibly unregistered.
25-Apr-2021 09:05:45.309 WARNING [Catalina-utility-2] org.apache.catalina.loader.WebappClassLoaderBase.clearReferencesThreads The web application [ciamdev] appears to have started a thread named [mssql-jdbc-shared-timer-core-0] 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.park(LockSupport.java:175)
 java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)
 java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:1081)
 java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:809)
 java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1074)
 java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1134)
 java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
 java.lang.Thread.run(Thread.java:748)
25-Apr-2021 09:05:45.310 WARNING [Catalina-utility-2] org.apache.catalina.loader.WebappClassLoaderBase.clearReferencesThreads The web application [ciamdev] appears to have started a thread named [HikariPool-113 housekeeper] but has failed to stop it. This is very likely to create a memory leak.
25-Apr-2021 09:05:45.312 WARNING [Catalina-utility-2] org.apache.catalina.loader.WebappClassLoaderBase.clearReferencesThreads The web application [ciamdev] appears to have started a thread named [HikariPool-113 connection adder] but has failed to stop it. This is very likely to create a memory leak. Stack trace of thread:

基本上4个主要事件:

com.microsoft.sqlserver.jdbc.SQLServerDriver is forcibly removed
mssql-jdbc-shared-timer-core-0 thread was unable to shut down
HikariPool-113 housekeeper thread was unable to shut down
HikariPool-113 connection adder thread was unable to shut down

当我不断调用数据库,然后重新部署时,HikariPool 线程开始成倍增加。

经过一番研究,我决定尝试一个涉及 ServletContextListener 的解决方案

我现在将发布相关信息/代码:

这是Tomcat版本:

Using CATALINA_BASE:   /apps/tomcat/apache-tomcat-9.0.34
Using CATALINA_HOME:   /apps/tomcat/apache-tomcat-9.0.34
Using CATALINA_TMPDIR: /apps/tomcat/apache-tomcat-9.0.34/temp
Using JRE_HOME:        /usr
Using CLASSPATH:       /apps/tomcat/apache-tomcat-9.0.34/bin/bootstrap.jar:/apps/tomcat/apache-tomcat-9.0.34/bin/tomcat-juli.jar
Server version: Apache Tomcat/9.0.34
Server built:   Apr 3 2020 12:02:52 UTC
Server number:  9.0.34.0
OS Name:        Linux
OS Version:     3.10.0-1160.24.1.el7.x86_64
Architecture:   amd64
JVM Version:    1.8.0_275-b01
JVM Vendor:     Red Hat, Inc.
Apache Maven version 3.8.1

这些是我的 pom.xml 依赖项(我在另一篇文章中添加了 HikariCP 排除标记):

    <dependencies>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>        

        <dependency>
            <groupId>com.squareup.okhttp3</groupId>
            <artifactId>okhttp</artifactId>
            <version>3.11.0</version>
        </dependency>

        <dependency>
            <groupId>com.jcraft</groupId>
            <artifactId>jsch</artifactId>
            <version>0.1.55</version>
        </dependency>

        <dependency>
            <groupId>com.googlecode.json-simple</groupId>
            <artifactId>json-simple</artifactId>
            <version>1.1.1</version>
        </dependency>

        <dependency>
            <groupId>com.microsoft.sqlserver</groupId>
            <artifactId>mssql-jdbc</artifactId>
            <scope>runtime</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>com.zaxxer</groupId>
                    <artifactId>HikariCP</artifactId>
                </exclusion>
            </exclusions>            
        </dependency>      

    </dependencies>

我创建了一个 Db 类,在其中实例化 jdbcTemplate 并添加数据源,还实现了 ServletContextListener,这是整个类,我确定我搞砸了,这里是类:

public class Db implements ServletContextListener {

  String message;

  @Override
  public void contextInitialized(ServletContextEvent servletContextEvent) {
    message = "contextInitialized\n\n";
    System.out.println("contextInitialized Server stopped");
  }

  @Override
  public void contextDestroyed(ServletContextEvent servletContextEvent) {
    message += "contextDestroyed\n\n";
    System.out.println("contextDestroyed Server stopped");

    Enumeration<Driver> drivers = DriverManager.getDrivers();
    while (drivers.hasMoreElements()) {
      Driver driver = drivers.nextElement();
      try {
        DriverManager.deregisterDriver(driver);
      } catch (SQLException e) {
        message += "ERROR(contextDestroyed): " + Utils.getStackTrace(e);
      }
    }
  }

  JdbcTemplate jdbcTemplate;

  public Db() {
    jdbcTemplate = new JdbcTemplate(sqlServerDataSource());
  }

  @Bean(destroyMethod = "shutdownNow")
  private DataSource sqlServerDataSource() {
    DataSourceBuilder dataSourceBuilder = DataSourceBuilder.create();
    dataSourceBuilder.url(
      "jdbc:sqlserver://sbserver.net;databaseName=gg"
    );
    dataSourceBuilder.username("user");
    dataSourceBuilder.password("pass");
    return dataSourceBuilder.build();
  }

  public String runSome() {
    message = "From runSome: \n\n";

    try {
      message += "trying to execute jdbcTemplate...\n";
      String sql = "select * from gg.objecttypes";

      List<ObjectType> objectTypes = jdbcTemplate.query(
        sql,
        new BeanPropertyRowMapper(ObjectType.class)
      );

      message += "\n\n";
      for (final ObjectType objectType : objectTypes) {
        message +=
          objectType.getId() + " :: " + objectType.getObjecttype() + "\n";
      }

      message +=
        "::executed jdbcTemplate! " + objectTypes.size() + "\n";
    } catch (Exception e) {
      message += "ERROR(runSome): " + Utils.getStackTrace(e);
    }

    return message;
  }
}

我什至不确定 contextInitialized 和 contextDestroyed 方法是否正在执行。

然后我可以从我的控制器调用该方法:

  @GetMapping(value = "/admin/user-health")
  public String userHealth() {

    String message = "From userHealth: \n\n";
    try {
        message += "trying to execute db.runSome()...\n";
        Db db = new Db();
        message += db.runSome();
        message += "executed db.runSome()!\n";
    } catch (Exception e) {
        message += "ERROR(userHealth): " + Utils.getStackTrace(e);
    }
    return message;

  }

同样,它工作正常,我从数据库中得到结果。尽管最终我得到了连接超时。

问题是内存泄漏,因为线程永远不会死,而且我认为我没有用户。

我怎样才能解决这个问题?

标签: spring-boottomcatjdbcmemory-leakshikaricp

解决方案


推荐阅读