Bug 51640

Summary: clearReferencesJdbc seems to be causing leaks with com.oracle.ojdbc5 driver
Product: Tomcat 7 Reporter: Patrick M <pmohr98>
Component: CatalinaAssignee: Tomcat Developers Mailing List <dev>
Severity: normal CC: pmohr98
Priority: P2    
Version: 7.0.11   
Target Milestone: ---   
Hardware: PC   
OS: Windows XP   
Attachments: Example Maven project to reproduce the problem

Description Patrick M 2011-08-10 00:21:38 UTC
Created attachment 27365 [details]
Example Maven project to reproduce the problem

clearReferencesJdbc seems to be causing reference leaks when used with com.oracle.ojdbc5 version, even if that class isn't loaded by the application.  As best as I can tell, that function is loading one or more classes from the jar.  Those classes are then adding a jmx MBean which then causes a reference leak.

Error message:
Aug 9, 2011 4:53:11 PM org.apache.catalina.loader.WebappClassLoader clearReferencesJdbc
SEVERE: The web application [/mavenproject1-1.0-SNAPSHOT] registered the JDBC driver [oracle.jdbc.OracleDriver] but failed to unregister it when the web application was stopped. To prevent a memory leak, the JDBC Driver has been forcibly unregistered.

List of classes in the class-loader:
0 : class org.apache.catalina.loader.JdbcLeakPrevention (84 bytes)
1 : class oracle.jdbc.driver.OracleDriver (84 bytes)
2 : class oracle.jdbc.OracleDriver (84 bytes)
3 : class oracle.jdbc.driver.OracleDriverExtension (84 bytes)
4 : class oracle.jdbc.driver.OracleDriver$1 (84 bytes)
5 : class oracle.jdbc.driver.DiagnosabilityMXBean (84 bytes)
6 : class oracle.jdbc.driver.OracleDiagnosabilityMBean (84 bytes)
7 : class oracle.jdbc.driver.DatabaseError (84 bytes)
8 : class oracle.jdbc.driver.OracleSQLException (84 bytes)
9 : class oracle.net.ns.NetException (84 bytes)
10 : class oracle.jdbc.driver.SQLStateMapping (84 bytes)
11 : class oracle.jdbc.driver.SQLStateMapping$Tokenizer (84 bytes)
12 : class oracle.jdbc.driver.Message (84 bytes)
13 : class oracle.jdbc.driver.Message11 (84 bytes)
14 : class oracle.jdbc.internal.ObjectDataFactory (84 bytes)
15 : class oracle.sql.ORADataFactory (84 bytes)
16 : class oracle.sql.AnyDataFactory (84 bytes)
17 : class oracle.jdbc.internal.ObjectData (84 bytes)
18 : class oracle.sql.ORAData (84 bytes)
19 : class oracle.sql.TypeDescriptorFactory (84 bytes) 

I believe this means that JdbcLeakPrevention is the first class to actually be loaded.

Steps to reproduce:
1) Compile the project, using the driver from http://www.oracle.com/technetwork/database/enterprise-edition/jdbc-111060-084321.html
2) Deploy the project to tomcat.
3) Undeploy the project from tomcat.  You should get the error about clearReferencesJdbc

While I would expect Tomcat to not leak the class, I would be happy if there was an option to make Tomcat not run clearReferencesJdbc, allowing me to undeploy the app without leaking.  This would not be as much of a problem if it wasn't causing the entire classloader and everything that implies to be leaked too.
Comment 1 Mark Thomas 2011-08-10 13:42:42 UTC
clearReferencesJdbc should not (and does not) leave any JDBC drivers loaded.

However, clearReferencesJdbc uses DriverManager to see what JDBC drivers are currently loaded. Unfortunately, the first ever call to DriverManager has the side-effect that any JDBC drivers in the current class loader will also be loaded. clearReferencesJdbc does ensure that any Driver loaded that way is immediately unloaded but when the Oracle driver is loaded it also creates the MBean and that triggers the leak.

The automatic creation of the MBean on driver load looks like a bug to me since there is no way for that MBean to be automatically destroyed when the Driver is unloaded. To my mind these things should be symmetric. You load the driver, you unload the driver. The MBean is automatically created, it should be automatically destroyed. Neither DriverManager nor Oracle's JDBC driver appear to follow this principle. Sigh.

What I also realised while looking at this is that only the first web application to use DriverManager will have it's JDBC drivers automatically loaded (since initialised is a static in DriverManager). That could lead to an application working or not working depending on application start order.

It looks like the best way to fix both issues is to call DriverManager.getDrivers() from the JreMemoryLeakPreventionListener. That fixes the problem described here and ensures that web application behaviour will be consistent regardless of start order and usage of DriverManager.

Note that even with this fix, if you use that Driver in a web application you will get a leak caused by the creation of the MBean.

This has been fixed in trunk and 7.0.x and will be included in 7.0.21 onwards.
Comment 2 Patrick M 2011-08-10 16:09:23 UTC
Sorry if this is an abuse of the bug tracker, but I'm hoping you might be able to answer two questions about this so I have a workaround:

1) If I make sure to load the driver every time, then consistently unload it every time, then unload the MBean every time, should that work before and after the fix?

2) If I put the jar in $CATALINA/lib, should that make sure the app's classloader is never leaked?

Finally, that you for the quick help.
Comment 3 Konstantin Kolinko 2011-10-19 08:18:25 UTC
(In reply to comment #1)
> This has been fixed in trunk and 7.0.x and will be included in 7.0.21 onwards.

Backported to 6.0 in r1186014 and will be in 6.0.34 onwards.

Documentation regarding SQL DriverManager class has been added to the "JDBC DataSources" page in documentation (jndi-datasource-examples-howto.html).