首页 文章

为防止内存泄漏,JDBC驱动程序已被强制取消注册

提问于
浏览
300

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

SEVERE:Web应用程序注册了JBDC驱动程序[oracle.jdbc.driver.OracleDriver],但在Web应用程序停止时无法取消注册 . 为防止内存泄漏,JDBC驱动程序已被强制取消注册 .

任何帮助赞赏 .

14 回答

  • 1

    从版本6.0.24开始,Tomcat附带了一个memory leak detection功能,当使用ServiceLoader API在webapp启动期间自动registers自身registers时,它会导致这种警告消息,但在此过程中没有自动deregister本身webapp的关闭 . 这条消息纯粹是非正式的,Tomcat已经相应地采取了内存泄漏防护措施 .

    你能做什么?

    • 忽略这些警告 . Tomcat正在做好自己的工作 . 实际的错误在于其他人's code (the JDBC driver in question), not in yours. Be happy that Tomcat did its job properly and wait until the JDBC driver vendor get it fixed so that you can upgrade the driver. On the other hand, you aren'应该在webapp的 /WEB-INF/lib 中删除JDBC驱动程序,但仅限于服务器的 /lib . 如果你仍然将它保存在webapp的 /WEB-INF/lib 中,那么你应该使用 ServletContextListener 手动注册和取消注册 .

    • 降级到Tomcat 6.0.23或更早版本,这样您就不会被这些警告所困扰 . 但它会默默地不断泄露记忆 . 不知道毕竟这是否有用 . 这些内存泄漏是Tomcat hotdeployments中OutOfMemoryError issues背后的主要原因之一 .

    • 将JDBC驱动程序移动到Tomcat的 /lib 文件夹,并使用连接池数据源来管理驱动程序 . 请注意,Tomcat的内置DBCP在关闭时不会正确注销驱动程序 . 另见bug DBCP-322,它被关闭为WONTFIX . 您更愿意将DBCP替换为另一个连接池,该连接池比DBCP更好地完成其工作 . 例如HikariCPBoneCP,或者Tomcat JDBC Pool .

  • 25

    在您的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);
                }
    
            }
    
  • 283

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

    However, the methodology of blanket driver deregistration is dangerous. DriverManager.getDrivers() 方法返回的某些驱动程序可能已由父ClassLoader加载(即,servlet容器's classloader) not the webapp context'的ClassLoader(例如,它们可能位于容器's lib folder, not the webapp'中,因此在整个容器中共享) . 取消注册这些驱动程序将影响任何其他可能正在使用它们的webapps(甚至是容器本身) .

    因此,在取消注册之前,应检查每个驱动程序的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);
            }
        }
    }
    
  • 8

    我看到这个问题出现了很多 . 是的,Tomcat 7会自动取消注册,但它真的可以控制你的代码和良好的编码实践吗?当然,您想知道您拥有所有正确的代码来关闭所有对象,关闭数据库连接池线程,并删除所有警告 . 我当然这样做 .

    我就是这样做的 .

    Step 1: Register a Listener

    web.xml中

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

    Step 2: Implement the Listener

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

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

  • 2

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

  • 6

    如果您从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>
    
  • 0

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

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

    重要提示:它应该被用于 ONLY when the driver jar is deployed in 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");
            }
    
        }
    
    }
    
  • 6

    我会补充这是我在Spring论坛上发现的东西 . 如果将JDBC驱动程序jar移动到tomcat lib文件夹,而不是使用webapp将其部署,则警告似乎消失了 . 我可以确认这对我有用

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

  • 157

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

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

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

    按照URL http://wiki.apache.org/tomcat/MemoryLeakProtectionReferences 部分的建议将JDK升级到 6u21 解决了Java堆空间问题(上下文现在重新加载正常),尽管仍然出现JDBC驱动程序错误 .

  • 0

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

    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这启发了我的错误修复 .

  • 76

    我发现Tomcat版本6.026存在同样的问题 .

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

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

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

  • 14

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

    谢谢 .

  • -10

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

相关问题