To prevent a memory leak, the JDBC Driver has been forcibly unregistered
Asked Answered
E

14

354

I am getting this message when I run my web application. It runs fine but I get this message during shutdown.

SEVERE: A web application registered the JBDC driver [oracle.jdbc.driver.OracleDriver] but failed to unregister it when the web application was stopped. To prevent a memory leak, the JDBC Driver has been forcibly unregistered.

Any help appreciated.

Effluence answered 23/7, 2010 at 16:45 Comment(1)
Could be duplicate of stackoverflow.com/questions/2604630/…Crossway
L
323

Since version 6.0.24, Tomcat ships with a memory leak detection feature, which in turn can lead to this kind of warning messages when there's a JDBC 4.0 compatible driver in the webapp's /WEB-INF/lib which auto-registers itself during webapp's startup using the ServiceLoader API, but which did not auto-deregister itself during webapp's shutdown. This message is purely informal, Tomcat has already taken the memory leak prevention action accordingly.

What can you do?

  1. Ignore those warnings. Tomcat is doing its job right. The actual bug is in someone else'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't supposed to drop a JDBC driver in webapp's /WEB-INF/lib, but only in server's /lib. If you still keep it in webapp's /WEB-INF/lib, then you should manually register and deregister it using a ServletContextListener.

  2. Downgrade to Tomcat 6.0.23 or older so that you will not be bothered with those warnings. But it will silently keep leaking memory. Not sure if that's good to know after all. Those kind of memory leaks are one of the major causes behind OutOfMemoryError issues during Tomcat hotdeployments.

  3. Move the JDBC driver to Tomcat's /lib folder and have a connection pooled datasource to manage the driver. Note that Tomcat's builtin DBCP does not deregister drivers properly on close. See also bug DBCP-322 which is closed as WONTFIX. You would rather like to replace DBCP by another connection pool which is doing its job better then DBCP. For example HikariCP or perhaps Tomcat JDBC Pool.

Lexy answered 23/7, 2010 at 17:1 Comment(16)
This is good advice. It's not warning of a memory leak, it's a warning that Tomcat took some forcible action to prevent a leakDoom
@BalusC, we are not using Tomcat's connection pool, do you think the message should still show up??Effluence
can we keep it this way or do you suggest to use the fix shown in DBCP-322 that you provided in the answer? I was wondering if we could just ignore those warnings! Thanks for your quick responses...Effluence
I have the same issue as in mona's comment. We're using C3P0 in Spring, but still get the issue. And I'm even running Tomcat 6.0.23, which is below 6.0.24.Aparejo
If option (1) is the way to go, why does Tomcat log these as SEVERE, though? SEVERE to me means "page the admin", not "ignore".Unitive
Why not do it yourself - rather than expect Tomcat to do it. In my opinion, it's not Tomcat's job to clean up our messy code. See my answer below.Nemertean
Just pointing out that this error can cause your session state to be invalid after reloading. I was having that problem until I used the solution mentioned by ae6rt to deregister the drivers.Herzog
@sproketboy: Huh? Was you assigning JDBC artifacts as fields of a class which is in turn stored in the HTTP session?Lexy
I guess it's usually reason 3 (having the library in the wAR as opposed to lib).Dover
@Lexy Does this warning comes when we restart the tomcat server???Does this warning cause tomcat to stop.My tomcat server is being stopped within 2 or 3 days after i started it.I deployed my webapp and it's been up and running for some time and one day it suddenly crashed.Is this could be the reason.I found below lines as something unusual in catalina.out file....Louvain
@Lexy 01-Apr-2017 03:24:31.125 INFO [Thread-5] org.apache.coyote.AbstractProtocol.pause Pausing ProtocolHandler ["http-nio-8080"] 01-Apr-2017 03:24:31.127 INFO [Thread-5] org.apache.coyote.AbstractProtocol.pause Pausing ProtocolHandler ["ajp-nio-8009"] 01-Apr-2017 03:24:31.127 INFO [Thread-5] org.apache.catalina.core.StandardService.stopInternal Stopping service Catalina 01-Apr-2017 03:24:31.199 WARNING [localhost-startStop-2] org.apache.catalina.loader.WebappClassLoaderBase.clearReferencesJdbc The web application [MyWebProject] registered the JDBC driver [com.mysql.jdbc.Driver] ...Louvain
@Lexy but failed to unregister it when the web application was stopped. To prevent a memory leak, the JDBC Driver has been forcibly unregistered. 01-Apr-2017 03:24:31.200 WARNING [localhost-startStop-2] org.apache.catalina.loader.WebappClassLoaderBase.clearReferencesThreads The web application [MyWebProject] appears to have started a thread named [Abandoned connection cleanup thread] but has failed to stop it. This is very likely to create a memory leak.Louvain
my production tomcat restarts it self after this warning so its reallly sever with memory leaks hard to traceDepict
This is not right. Threads remain open as you can see with jstack.Bootlick
why suggest to move mysql lib to <CATALINA_HOME>/lib? Should be in webapp lib directory.. unless there is a reason to move to the CATALINA_HOME.Angadreme
@Jasonw: to utilize server-managed connection pooling. Related: https://mcmap.net/q/17054/-how-should-i-connect-to-jdbc-database-datasource-in-a-servlet-based-applicationLexy
I
171

In your servlet context listener contextDestroyed() method, manually deregister the drivers:

// 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);
    }
}
Inutile answered 15/3, 2011 at 17:20 Comment(2)
It works! javabeat.net/servletcontextlistener-example may help to implement servlet context listenerBagnio
This is potentially unsafe in a shared environment as you might not want to deregister all JDBC drivers that are available. See my answer for a safer approach.Atwekk
A
102

Although Tomcat does forcibly deregister the JDBC driver for you, it is nonetheless good practice to clean up all resources created by your webapp on context destruction in case you move to another servlet container which doesn't do the memory leak prevention checks that Tomcat does.

However, the methodology of blanket driver deregistration is dangerous. Some drivers returned by the DriverManager.getDrivers() method may have been loaded by the parent ClassLoader (i.e., the servlet container's classloader) not the webapp context's ClassLoader (e.g., they may be in the container's lib folder, not the webapp's, and therefore shared across the whole container). Deregistering these will affect any other webapps which may be using them (or even the container itself).

Therefore, one should check that the ClassLoader for each driver is the webapp's ClassLoader before deregistering it. So, in your ContextListener's contextDestroyed() method:

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);
        }
    }
}
Atwekk answered 28/5, 2014 at 12:56 Comment(6)
Shouldn't be if (cl.equals(driver.getClass().getClassLoader())) { ?Kv
@Kv No, we are checking if it is precisely the same ClassLoader instance, not if it is two separate instances which are equal in value.Atwekk
Although the others seem to correctly handle the problem in question, they cause problems when the war file is deleted and then replaced. In that case the drivers are de-registered and never return - only a Tomcat restart can get you out of that hole. This solution avoids that hell.Bootleg
Here mostly same but with additional MySQL/MariaDB handling code github.com/spring-projects/spring-boot/issues/2612Solidify
If you use H2 or PostgreSQL this results in the driver not getting registered again upon reload. Both drivers maintain an internal registration state that isn't cleared if the driver is just deregistered from the DriverManager. I left a more detailed comment at github.com/spring-projects/spring-boot/issues/…Algy
@MarcelStör You are right, I got trouble using the double-hash-functionality in tomcat. A rollback to the original war-version fail because the driver is not available anymore.Manis
N
28

I see this issue come up a lot. Yes, Tomcat 7 does automatically deregister it, but it that REALLY taking control of your code and a good coding practice? Surely YOU want to know that you have all the correct code in place to close all your objects, shut down database connection pool threads, and get rid of all warnings. I certainly do.

This is how I do it.

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

}

Please feel free to comment and/or add...

Nemertean answered 19/10, 2011 at 10:47 Comment(9)
It looks so lovely when your application ends and your logs just say: [INFO]Shutting down application. [INFO]Closing connection pool. [INFO]Deregistering MySQLDriver. [INFO]Closing Log FileHandler.Nemertean
But DataSource does NOT have a close methodArrow
Yes, there is no method close() for DataSource.Damalas
BasicDataSource has a close()Thresher
Funnily enough, it was there in the version I was using :) Removed it as it's no longer there. The dataSource = null will do the trick instead.Nemertean
should it be implements javax.servlet.ServletContextListener, not extends ApplicationContextListener?Tightlipped
Is there a reason for the order of those operations in the method contextDestroyed? Why do you do step 1. before doing step 2., where initContext, envContext and datasource are not referenced at all? I'm asking because I don't understand step 3.Abaft
@Abaft I think Step 1. is not necessary, lookup seems to just gets some objects you don't need. Step 3. is completly useless. It certainly doesn't add any safety and looks like something beginners would do who don't understand how GC works. I would just go with https://mcmap.net/q/18243/-to-prevent-a-memory-leak-the-jdbc-driver-has-been-forcibly-unregistered and remove all drivers.Elatia
@kapep Removing all drivers is dangerous as some might be shared across the container. See my answer for an approach which only removes the drivers loaded by your webapp's ClassLoader.Atwekk
Z
14

This is purely driver registration/deregistration issue in mysql`s driver or tomcats webapp-classloader. Copy mysql driver into tomcats lib folder (so its loaded by jvm directly, not by tomcat), and message will be gone. That makes mysql jdbc driver to be unloaded only at JVM shutdown, and noone cares about memory leaks then.

Zephan answered 12/9, 2010 at 22:50 Comment(2)
that don't works... I tried to copy the jdbc driver has you say: TOMCAT_HOME/lib/postgresql-9.0-801.jdbc4.jar - Tomcat 7.0\lib\postgresql-9.0-801.jdbc4.jar with no results...Announce
@Florito - you have to remove it from your web-apps WEB-INF/lib as wellIndignant
B
8

If you are getting this message from a Maven built war change the scope of the JDBC driver to provided, and put a copy of it in the lib directory. Like this:

<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>
Bee answered 13/9, 2012 at 10:15 Comment(0)
L
8

Solution for per-app deployments

This is a listener I wrote to solve the problem: it autodetects if the driver has registered itself and acts accordingly.it

Important: it is meant to be used ONLY when the driver jar is deployed in WEB-INF/lib, not in the Tomcat /lib, as many suggest, so that each application can take care of its own driver and run on a untouched Tomcat. That is the way it should be IMHO.

Just configure the listener in your web.xml before any other and enjoy.

add near the top of web.xml:

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

save as 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");
        }

    }

}
Libra answered 4/4, 2013 at 16:7 Comment(2)
Tip: As of Servlet 3.0, you can annotate your class with @WebListener and omit the web.xml configuration.Topeka
True, just make sure that it is picked up with a priority high enough, so that no one needs to use the driver before or after.Libra
A
7

I found that implementing a simple destroy() method to de-register any JDBC drivers works nicely.

/**
 * 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");
}
Alidia answered 28/2, 2012 at 19:4 Comment(1)
This is potentially unsafe in a shared environment as you might not want to deregister all JDBC drivers that are available. See my answer for a safer approach. Also, this really should be done in a ServletContextListener, not on a per-servlet basis as your JDBC driver is shared across all of your servlets in your webapp.Atwekk
I
6

I will add to this something I found on the Spring forums. If you move your JDBC driver jar to the tomcat lib folder, instead of deploying it with your webapp, the warning seems to disappear. I can confirm that this worked for me

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

Indignant answered 4/7, 2011 at 22:0 Comment(1)
I think this offers a better solution - just subclass your DatasourceManager and override the close method to add deregistration. Then Spring will handle it when it destroys its context, and Tomcat won't give you the SEVERE log, and you don't have to move your JDBC driver into the lib dir. Here's the orignal msg from the old spring forumWight
M
4

To prevent this memory leak, simply deregister the driver on context shutdown.

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 that inspired me for this bug fix.

Meltage answered 1/5, 2018 at 17:23 Comment(0)
F
2

I was having a similar problem, but additionally I was getting a Java Heap Space error anytime I modified/saved JSP pages with Tomcat server running, therefore the context were not fully recharged.

My versions were Apache Tomcat 6.0.29 and JDK 6u12.

Upgrading JDK to 6u21 as suggested in References section of URL http://wiki.apache.org/tomcat/MemoryLeakProtection solved the Java Heap Space problem (context now reloads OK) although JDBC Driver error still appears.

Formalism answered 13/9, 2010 at 22:2 Comment(0)
B
0

I found the same issue with Tomcat version 6.026.

I used the Mysql JDBC.jar in WebAPP Library as well as in TOMCAT Lib.

To fix the above by removing the Jar from the TOMCAT lib folder.

So what I understand is that TOMCAT is handling the JDBC memory leak properly. But if the MYSQL Jdbc jar is duplicated in WebApp and Tomcat Lib, Tomcat will only be able to handle the jar present in the Tomcat Lib folder.

Breadbasket answered 24/4, 2012 at 16:3 Comment(0)
R
0

I have faced this problem when I was deploying my Grails application on AWS. This is matter of JDBC default driver org.h2 driver . As you can see this in the Datasource.groovy inside your configuration folder . As you can see below :

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

Comment those lines wherever there is mentioned org.h2.Driver in the datasource.groovy file , if you are not using that database . Otherwise you have to download that database jar file .

Thanks .

Reg answered 8/1, 2017 at 15:24 Comment(0)
D
0

This error happened to me in a Grails Application with the JTDS Driver 1.3.0 (SQL Server). The problem was an incorrect login in SQL Server. After solve this issue (in SQL Server) my app was correctly deployed in Tomcat. Tip: I saw the error in stacktrace.log

Davinadavine answered 21/1, 2020 at 2:17 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.