为了防止内存泄漏,jdbc驱动程序已强制取消注册?

内容来源于 Stack Overflow,并遵循CC BY-SA 3.0许可协议进行翻译与使用

  • 回答 (10)
  • 关注 (0)
  • 查看 (5192)

我的Web程序正常运行,但是在关闭程序的时候我收到了这条信息。

web应用程序注册了JBDC驱动程序。oracle.jdbc.driver.OracleDriver但是,当web应用程序停止时,未能注销它。为了防止内存泄漏,JDBC驱动程序被迫取消注册。

提问于
用户回答回答于

从版本6.0.24开始,Tomcat提供了一个内存泄漏检测功能,当webapp的/ WEB-INF / lib中有一个兼容JDBC 4.0的驱动程序,它会在webapp启动时auto-register自己使用的ServiceLoaderAPI,但在webapp关闭期间没有自动注销。这个消息纯属非正式的,Tomcat已经相应地采取了内存泄漏预防行动。

可以采取以下措施:

  1. 忽略这些警告。 Tomcat正在做正确的工作。 实际的bug是在别人的代码中(所讨论的JDBC驱动程序),而不是在你的代码中。 Tomcat在正常工作,并等待JDBC驱动程序供应商解决问题,以便升级驱动程序。 另一方面,您不应该在webapp的/ WEB-INF / lib中放置JDBC驱动程序,而只能在服务器的/ lib中放置JDBC驱动程序。 如果你仍然保存在webapp的/ WEB-INF / lib中,那么你应该使用ServletContextListener手动注册和注销它。
  2. 降级到Tomcat 6.0.23或更低版本,这样你就不会被那些警告所困扰。但它会悄悄地泄漏内存。这些内存泄漏是Tomcat的OutOfMemoryError问题背后的主要原因之一。
  3. 将JDBC驱动程序移动到Tomcat/lib文件夹,并有一个连接池数据源来管理驱动程序。请注意,Tomcat的内置DBCP在关闭时不会正确注销驱动程序。另请参阅作为WONTFIX关闭的bug DBCP-322。您可以将DBCP替换为另一个更好的DBCP连接池。例如HikariCPBoneCP,或Tomcat JDBC池
用户回答回答于

当我在Tomcat服务器运行时修改/保存JSP页面时,我还会得到一个JavaHeapSpace error。

我的版本是ApacheTomcat 6.0.29和JDK6u12。

按照URL的References部分的建议将JDK升级到6u21

http://wiki.apache.org/tomcat/MemoryLeakProtection,解决了JavaHeapSpace问题(上下文现在重新加载没问题),虽然还会出现JDBC驱动程序错误。

用户回答回答于

实现一个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");
}
用户回答回答于

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

这是我为解决这个问题而写的一个侦听器:它自动检测驱动程序是否注册了自己并采取相应的行动,它就会自动检测。

重要提示:仅当驱动程序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");
        }

    }

}
用户回答回答于

如果你从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>
用户回答回答于

在MySQL的驱动程序或tomcat的Web应用程序类加载器,这是纯粹的驱动程序注册/注销问题。 将mysql驱动复制到tomcats lib文件夹(它由jvm直接加载,而不是由tomcat加载),并且消息将会消失。 这使得mysql jdbc驱动程序只能在JVM关闭时被卸载,而没有人关心内存泄漏。

用户回答回答于

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

}
用户回答回答于

尽管Tomcat强制注销了JDBC驱动程序,但是,如果移动到另一个不执行Tomcat内存泄漏阻止检查的servlet容器,则会清除由Web应用程序创建的所有上下文销毁资源。

然而,全面的driver注销方法是危险的。 由DriverManager.getDrivers()方法返回的一些驱动程序可能已经由DriverManager.getDrivers() (即,servlet容器的类加载器)加载而不是由webapp上下文的类加载器加载(例如,它们可能位于容器的lib文件夹中,而不是web应用程序的, 共享整个容器)。 注销这些会影响任何可能使用它们的webapps(甚至是容器本身)。

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

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);
        }
    }
}
用户回答回答于

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

        }

所属标签

扫码关注云+社区

领取腾讯云代金券