时间:2010-07-23 16:45:16

标签: java tomcat jdbc

15 个答案:

答案 0 :(得分:287)

答案 1 :(得分:160)

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

        }

答案 2 :(得分:76)

尽管Tomcat强制为您取消注册JDBC驱动程序,但是如果您移动到另一个不执行内存泄漏防护检查的servlet容器,那么清理上下文销毁时由webapp创建的所有资源仍然是一种很好的做法。 Tomcat确实。

但是,一揽子驱动程序注销的方法是危险的。 DriverManager.getDrivers()方法返回的某些驱动程序可能已由父ClassLoader(即servlet容器的类加载器)加载而不是webapp context的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);
        }
    }
}

答案 3 :(得分:25)

我看到这个问题出现了很多。是的,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;
    }

}

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

答案 4 :(得分:14)

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

答案 5 :(得分:8)

如果从Maven构建的war获取此消息,请更改要提供的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>

答案 6 :(得分:8)

针对每个应用部署的解决方案

这是我为解决问题而写的一个监听器:如果驱动程序已经注册并且相应地采取行动,它会自动检测。它

重要说明:只有当驱动程序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");
        }

    }

}

答案 7 :(得分:6)

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

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

答案 8 :(得分:6)

我发现实现一个简单的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");
}

答案 9 :(得分:2)

我遇到了类似的问题,但是在我运行Tomcat服务器修改/保存JSP页面的任何时候我都收到了Java堆空间错误,因此上下文没有完全充电。

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

按照网址http://wiki.apache.org/tomcat/MemoryLeakProtection 参考 部分的建议将JDK升级到 6u21 解决了Java堆空间问题(上下文现在重新加载) OK)虽然仍然出现JDBC驱动程序错误。

答案 10 :(得分:1)

要防止此内存泄漏,只需在上下文关闭时取消注册驱动程序。

<强>的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>

Source激发了我对这个错误修复的启发。

答案 11 :(得分:0)

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

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

通过从TOMCAT lib文件夹中删除Jar来解决上述问题。

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

答案 12 :(得分:0)

我在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文件。

谢谢。

答案 13 :(得分:0)

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

中看到错误

答案 14 :(得分:-10)

删除应用程序(tomcat6)解决了它。 conf文件被保留。 它以某种方式打破了自己。我不确定它是如何做到的。