我在运行Web应用程序时收到此消息。它运行正常,但是在关机期间却收到此消息。


严重:一个Web应用程序注册了JBDC驱动程序[oracle.jdbc.driver.OracleDriver],但在该Web应用程序注册时未能取消注册被停止了。为防止内存泄漏,已强制取消注册JDBC驱动程序。


任何帮助,感激不尽。

评论

可能是stackoverflow.com/questions/2604630/…的副本...

#1 楼

从6.0.24版开始,Tomcat附带了内存泄漏检测功能,当webapp的/WEB-INF/lib中存在与JDBC 4.0兼容的驱动程序时,当使用ServiceLoader API在webapp的启动过程中自动注册自身时,它又会导致此类警告消息,但在webapp关闭期间不会自动注销自己。该消息纯粹是非正式的,Tomcat已经采取了相应的内存泄漏预防措施。
您该怎么办?


忽略那些警告。 Tomcat正在正确地执行其工作。实际的错误是在别人的代码(有问题的JDBC驱动程序)中,而不是您的代码中。对Tomcat能够正确完成工作感到高兴,并等待JDBC驱动程序供应商对其进行修复,以便您可以升级驱动程序。另一方面,您不应在Webapp的/WEB-INF/lib中放置JDBC驱动程序,而只能在服务器的/lib中放置JDBC驱动程序。如果仍将其保留在webapp的/WEB-INF/lib中,则应使用ServletContextListener手动注册和注销它。


降级到Tomcat 6.0.23或更早版本,这样您就不会为它们所困扰警告。但是它会默默地保持内存泄漏。毕竟不确定这是否很好。此类内存泄漏是Tomcat热部署期间OutOfMemoryError问题背后的主要原因之一。


将JDBC驱动程序移动到Tomcat的/lib文件夹,并具有连接池数据源来管理该驱动程序。请注意,Tomcat的内置DBCP在关闭时无法正确注销驱动程序。另请参见错误DBCP-322,该错误已作为WONTFIX关闭。您想用另一个连接池代替DBCP,该连接池比DBCP做得更好。例如HikariCP或Tomcat JDBC Pool。



评论


这是个好建议。这不是警告内存泄漏,而是警告Tomcat采取了一些强制性措施来防止泄漏

–马特b
2010年7月23日在17:43

如果要采用选项(1),那么Tomcat为什么将它们记录为SEVERE?对我来说,严重是指“页面管理员”,而不是“忽略”。

– Peter Becker
11-10-17在23:16

为什么不自己做呢-而不是期望Tomcat这样做。在我看来,清理我们凌乱的代码不是Tomcat的工作。请参阅下面的答案。

–sparkyspider
2011年10月19日上午11:24

@sproketboy:嗯?您是否将JDBC工件分配为类的字段,而该类又存储在HTTP会话中?

– BalusC
2013年6月27日23:44



我想这通常是原因3(在wAR中拥有库而不是lib)。

–lapo
2014年11月25日在9:42

#2 楼

在您的Servlet上下文侦听器contextDestroyed()方法中,手动注销驱动程序:

 // This manually deregisters JDBC driver, which prevents Tomcat 7 from complaining about memory leaks wrto this class
Enumeration<Driver> drivers = DriverManager.getDrivers();
while (drivers.hasMoreElements()) {
    Driver driver = drivers.nextElement();
    try {
        DriverManager.deregisterDriver(driver);
        LOG.log(Level.INFO, String.format("deregistering jdbc driver: %s", driver));
    } catch (SQLException e) {
        LOG.log(Level.SEVERE, String.format("Error deregistering driver %s", driver), e);
    }
}
 


评论


有用! javabeat.net/servletcontextlistener-example可能有助于实现servlet上下文侦听器

– Vadim Zin4uk
2013年12月4日12:44

在共享环境中,这可能是不安全的,因为您可能不希望注销所有可用的JDBC驱动程序。请参阅我的答案以获得更安全的方法。

– daiscog
2014年5月28日13:01

#3 楼

尽管Tomcat确实为您强制注销了JDBC驱动程序,但是,如果您移至另一个不执行Tomcat防止内存泄漏检查的servlet容器,则清理上下文上下文中由webapp创建的所有资源仍然是一个好习惯。

但是,一揽子司机注销的方法很危险。 DriverManager.getDrivers()方法返回的某些驱动程序可能是由父类ClassLoader(即servlet容器的classloader)而不是webapp上下文的ClassLoader加载的(例如,它们可能位于容器的lib文件夹中,而不是在webapp的文件夹中,因此在整个应用程序之间共享容器)。取消注册这些文件将影响可能正在使用它们的任何其他Web应用程序(甚至容器本身)。

因此,在注销之前,应检查每个驱动程序的ClassLoader是否是Webapp的ClassLoader。因此,在您的ContextListener的contextDestroyed()方法中:

public final void contextDestroyed(ServletContextEvent sce) {
    // ... First close any background tasks which may be using the DB ...
    // ... Then close any DB connection pools ...

    // 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);
        }
    }
}


评论


不应该是((cl.equals(driver.getClass()。getClassLoader())){吗?

–user11153
2014年5月28日13:13

@ user11153不,我们正在检查它是否是完全相同的ClassLoader实例,而不是是否它是两个单独的实例,它们的值相等。

– daiscog
2014年5月28日13:18

尽管其他文件似乎可以正确处理所讨论的问题,但是当删除战争文件然后将其替换时,它们会引起问题。在这种情况下,驱动程序将被注销并永远不会返回-只有Tomcat重新启动才能使您摆脱困境。该解决方案避免了这种麻烦。

–OldCurmudgeon
2014年7月2日13:50

这里基本相同,但带有附加的MySQL / MariaDB处理代码github.com/spring-projects/spring-boot/issues/2612

– gavenkoa
15年12月19日在9:59

如果您使用H2或PostgreSQL,这将导致驱动程序在重新加载时不再被注册。两个驱动程序都维护一个内部注册状态,如果只是从DriverManager中注销了该驱动程序,则不会清除该状态。我在github.com/spring-projects/spring-boot/issues/…上留下了更详细的评论。

– MarcelStör
18年6月29日在6:51

#4 楼

我看到这个问题很多。是的,Tomcat 7会自动取消注册,但是它真的可以控制您的代码和良好的编码习惯吗?当然,您想知道您已经准备好所有正确的代码来关闭所有对象,关闭数据库连接池线程并摆脱所有警告。我当然可以。

这就是我的方法。

步骤1:注册侦听器

web.xml

<listener>
    <listener-class>com.mysite.MySpecialListener</listener-class>
</listener>


步骤2:实现侦听器

com.mysite.MySpecialListener.java

public class MySpecialListener implements ServletContextListener {

    @Override
    public void contextInitialized(ServletContextEvent sce) {
        // On Application Startup, please…

        // Usually I'll make a singleton in here, set up my pool, etc.
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        // On Application Shutdown, please…

        // 1. Go fetch that DataSource
        Context initContext = new InitialContext();
        Context envContext  = (Context)initContext.lookup("java:/comp/env");
        DataSource datasource = (DataSource)envContext.lookup("jdbc/database");

        // 2. Deregister Driver
        try {
            java.sql.Driver mySqlDriver = DriverManager.getDriver("jdbc:mysql://localhost:3306/");
            DriverManager.deregisterDriver(mySqlDriver);
        } catch (SQLException ex) {
            logger.info("Could not deregister driver:".concat(ex.getMessage()));
        } 

        // 3. For added safety, remove the reference to dataSource for GC to enjoy.
        dataSource = null;
    }

}


请随时发表评论和/或添加...

评论


但是DataSource没有close方法

– Jim
2012年4月4日在8:16



它应该实现javax.servlet.ServletContextListener,而不是扩展ApplicationContextListener吗?

–egaga
13年3月23日在16:54



在contextDestroyed方法中,这些操作的顺序是否有原因?为什么在执行步骤2之前根本不引用initContext,envContext和数据源的情况下执行步骤1。我问是因为我不了解步骤3。

–马修斯
2013年9月23日12:02

@matthaeus我认为步骤1是不必要的,查找似乎只是获取了一些不需要的对象。步骤3.完全没有用。它当然不会增加任何安全性,并且看起来像初学者会做一些不了解GC如何工作的事情。我只是选择stackoverflow.com/a/5315467/897024并删除所有驱动程序。

– kapex
13-10-2在15:53

@kapep删除所有驱动程序很危险,因为某些驱动程序可能会在整个容器中共享。请参阅我的答案,以获取一种仅删除由Webapp的ClassLoader加载的驱动程序的方法。

– daiscog
2014年5月28日13:07



#5 楼

这纯粹是mysql的驱动程序或tomcats webapp-classloader中的驱动程序注册/注销问题。将mysql驱动程序复制到tomcats的lib文件夹中(因此它直接由jvm加载,而不是由tomcat加载),消息将消失。这使得mysql jdbc驱动程序仅在JVM关闭时才被卸载,然后再没有人关心内存泄漏。

评论


那行不通...我试图复制jdbc驱动程序,您是否说过:TOMCAT_HOME / lib / postgresql-9.0-801.jdbc4.jar-Tomcat 7.0 \ lib \ postgresql-9.0-801.jdbc4.jar没有结果...

–a11r
2011年6月17日12:50



@Florito-您还必须将其从Web应用程序WEB-INF / lib中删除

–科林·彼得斯
2011年7月5日在16:20

#6 楼

如果从Maven构建的战争中获得此消息,请将JDBC驱动程序的范围更改为提供的,并将其副本放在lib目录中。像这样:

<dependency>
  <groupId>mysql</groupId>
  <artifactId>mysql-connector-java</artifactId>
  <version>5.1.18</version>
  <!-- put a copy in /usr/share/tomcat7/lib -->
  <scope>provided</scope>
</dependency>


#7 楼

每个应用程序部署的解决方案

我写来解决此问题的侦听器:它可以自动检测驱动程序是否已注册自身并采取相应措施。

重要:它是意味着仅在将驱动程序jar部署在WEB-INF / lib中而不是在Tomcat / lib中部署时才使用,正如许多人建议的那样,以便每个应用程序都可以照顾自己的驱动程序并在未改动的Tomcat上运行。就是这样,应该是恕我直言。

只需在web.xml中配置侦听器,然后再享用它即可。

在web.xml顶部附近添加:

 <listener>
    <listener-class>utils.db.OjdbcDriverRegistrationListener</listener-class>    
</listener>
 


另存为utils / db / OjdbcDriverRegistrationListener.java:

 package utils.db;

import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Enumeration;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;

import oracle.jdbc.OracleDriver;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Registers and unregisters the Oracle JDBC driver.
 * 
 * Use only when the ojdbc jar is deployed inside the webapp (not as an
 * appserver lib)
 */
public class OjdbcDriverRegistrationListener implements ServletContextListener {

    private static final Logger LOG = LoggerFactory
            .getLogger(OjdbcDriverRegistrationListener.class);

    private Driver driver = null;

    /**
     * Registers the Oracle JDBC driver
     */
    @Override
    public void contextInitialized(ServletContextEvent servletContextEvent) {
        this.driver = new OracleDriver(); // load and instantiate the class
        boolean skipRegistration = false;
        Enumeration<Driver> drivers = DriverManager.getDrivers();
        while (drivers.hasMoreElements()) {
            Driver driver = drivers.nextElement();
            if (driver instanceof OracleDriver) {
                OracleDriver alreadyRegistered = (OracleDriver) driver;
                if (alreadyRegistered.getClass() == this.driver.getClass()) {
                    // same class in the VM already registered itself
                    skipRegistration = true;
                    this.driver = alreadyRegistered;
                    break;
                }
            }
        }

        try {
            if (!skipRegistration) {
                DriverManager.registerDriver(driver);
            } else {
                LOG.debug("driver was registered automatically");
            }
            LOG.info(String.format("registered jdbc driver: %s v%d.%d", driver,
                    driver.getMajorVersion(), driver.getMinorVersion()));
        } catch (SQLException e) {
            LOG.error(
                    "Error registering oracle driver: " + 
                            "database connectivity might be unavailable!",
                    e);
            throw new RuntimeException(e);
        }
    }

    /**
     * Deregisters JDBC driver
     * 
     * Prevents Tomcat 7 from complaining about memory leaks.
     */
    @Override
    public void contextDestroyed(ServletContextEvent servletContextEvent) {
        if (this.driver != null) {
            try {
                DriverManager.deregisterDriver(driver);
                LOG.info(String.format("deregistering jdbc driver: %s", driver));
            } catch (SQLException e) {
                LOG.warn(
                        String.format("Error deregistering driver %s", driver),
                        e);
            }
            this.driver = null;
        } else {
            LOG.warn("No driver to deregister");
        }

    }

}
 


评论


提示:从Servlet 3.0开始,您可以使用@WebListener注释类,并省略web.xml配置。

–罗勒·布尔克
16年5月12日在3:02



没错,只需确保以足够高的优先级启动它,这样就无需再使用该驱动程序了。

– Andrea Ratto
16年8月24日在1:32

#8 楼

我将在Spring论坛上找到的内容添加到其中。如果将JDBC驱动程序jar移到tomcat lib文件夹中,而不是与您的webapp一起部署,警告似乎消失了。我可以确认这对我有用

http://forum.springsource.org/showthread.php?87335-Failure-to-unregister-the-MySQL-JDBC-Driver&p=334883#post334883

评论


我认为这提供了更好的解决方案-仅将您的DatasourceManager子类化并重写close方法即可添加注销。然后,Spring将在破坏上下文时对其进行处理,并且Tomcat不会为您提供SEVERE日志,并且您不必将JDBC驱动程序移至lib目录中。这是旧春天论坛上的原始味精

–亚当
2014-09-24 12:42



#9 楼

我发现实现一个简单的destroy()方法来注销任何JDBC驱动程序的效果很好。

/**
 * Destroys the servlet cleanly by unloading JDBC drivers.
 * 
 * @see javax.servlet.GenericServlet#destroy()
 */
public void destroy() {
    String prefix = getClass().getSimpleName() +" destroy() ";
    ServletContext ctx = getServletContext();
    try {
        Enumeration<Driver> drivers = DriverManager.getDrivers();
        while(drivers.hasMoreElements()) {
            DriverManager.deregisterDriver(drivers.nextElement());
        }
    } catch(Exception e) {
        ctx.log(prefix + "Exception caught while deregistering JDBC drivers", e);
    }
    ctx.log(prefix + "complete");
}


评论


在共享环境中,这可能是不安全的,因为您可能不希望注销所有可用的JDBC驱动程序。请参阅我的答案以获得更安全的方法。另外,这实际上应该在ServletContextListener中完成,而不是在每个Servlet上完成,因为JDBC驱动程序在Web应用程序中的所有Servlet之间共享。

– daiscog
2014年5月28日在13:14

#10 楼

为了防止内存泄漏,只需在上下文关闭时注销驱动程序即可。

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.mywebsite</groupId>
    <artifactId>emusicstore</artifactId>
    <version>1.0-SNAPSHOT</version>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.7.0</version>
                <configuration>
                    <source>1.9</source>
                    <target>1.9</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

    <dependencies>
        <!-- ... -->

        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-core</artifactId>
            <version>4.0.1.Final</version>
        </dependency>

        <dependency>
            <groupId>org.hibernate.javax.persistence</groupId>
            <artifactId>hibernate-jpa-2.0-api</artifactId>
            <version>1.0.1.Final</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.11</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/javax.servlet/servlet-api -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>servlet-api</artifactId>
            <version>2.5</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

</project>


MyWebAppContextListener.java

package com.emusicstore.utils;

import com.mysql.cj.jdbc.AbandonedConnectionCleanupThread;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Enumeration;

public class MyWebAppContextListener implements ServletContextListener {

    @Override
    public void contextInitialized(ServletContextEvent servletContextEvent) {
        System.out.println("************** Starting up! **************");
    }

    @Override
    public void contextDestroyed(ServletContextEvent servletContextEvent) {
        System.out.println("************** Shutting down! **************");
        System.out.println("Destroying Context...");
        System.out.println("Calling MySQL AbandonedConnectionCleanupThread checkedShutdown");
        AbandonedConnectionCleanupThread.checkedShutdown();

        ClassLoader cl = Thread.currentThread().getContextClassLoader();

        Enumeration<Driver> drivers = DriverManager.getDrivers();
        while (drivers.hasMoreElements()) {
            Driver driver = drivers.nextElement();

            if (driver.getClass().getClassLoader() == cl) {
                try {
                    System.out.println("Deregistering JDBC driver {}");
                    DriverManager.deregisterDriver(driver);

                } catch (SQLException ex) {
                    System.out.println("Error deregistering JDBC driver {}");
                    ex.printStackTrace();
                }
            } else {
                System.out.println("Not deregistering JDBC driver {} as it does not belong to this webapp's ClassLoader");
            }
        }
    }

}


web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
                             http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">

    <listener>
        <listener-class>com.emusicstore.utils.MyWebAppContextListener</listener-class>
    </listener>

<!-- ... -->

</web-app>


启发了我此错误修复程序的来源。

#11 楼

我遇到了类似的问题,但是此外,每当我在运行Tomcat服务器的情况下修改/保存JSP页面时,都会收到Java Heap Space错误,因此上下文没有完全充电。

我的版本是Apache Tomcat 6.0.29和JDK 6u12。

按照URL http://wiki.apache.org/tomcat的“参考”部分中的建议将JDK升级到6u21。 / MemoryLeakProtection解决了Java堆空间问题(现在重新加载OK),尽管JDBC Driver错误仍然出现。

#12 楼

我在Tomcat 6.026版本中发现了相同的问题。

我在WebAPP库以及TOMCAT Lib中使用了Mysql JDBC.jar。

通过删除以下内容来解决上述问题来自TOMCAT lib文件夹的jar。

所以我了解的是TOMCAT正在正确处理JDBC内存泄漏。但是,如果在WebApp和Tomcat Lib中复制了MYSQL Jdbc jar,则Tomcat将只能处理Tomcat Lib文件夹中存在的jar。

#13 楼

当我在AWS上部署Grails应用程序时遇到了这个问题。这是JDBC默认驱动程序org.h2驱动程序的问题。
正如您可以在配置文件夹中的Datasource.groovy中看到的那样。如下所示:

dataSource {
    pooled = true
    jmxExport = true
    driverClassName = "org.h2.Driver"   // make this one comment
    username = "sa"
    password = ""
}


注释掉datasource.groovy文件中提到org.h2.Driver的那些行,如果您没有使用该数据库的话。
否则,您必须下载该数据库jar文件。

,谢谢。

#14 楼

这个错误发生在使用JTDS驱动程序1.3.0(SQL Server)的Grails应用程序中。问题是在SQL Server中登录不正确。解决此问题后(在SQL Server中),我的应用已正确部署在Tomcat中。提示:我在stacktrace.log中看到错误