To prevent a memory leak, the JDBC Driver has been forcibly unregistered
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.
Solution 1:
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?
-
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 aServletContextListener
. -
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. -
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.
Solution 2:
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);
}
}
Solution 3:
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);
}
}
}