1. 概述

在这个教程中,我们将探讨Tomcat警告消息,它告诉我们它强制注销了一个JDBC驱动器。我们将深入理解这个消息的含义、根本原因以及如何减轻这个问题。

2. 警告信息与含义

警告消息的一个版本可能是这样的:

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.

通过上述信息,Tomcat告知我们,当部署web应用时,JDBC驱动类OracleDriver被注册,但在卸载相同应用时并未注销。

加载和注册JDBC驱动器的方式有多种,这本质上是一个实现了java.sql.Driver接口的类。Tomcat使用Java服务提供者接口,并会自动加载在web应用的WEB-INF/lib目录下找到的任何兼容JDBC 4.0的驱动类。

当我们卸载web应用时,也需要注销它带来的所有驱动。否则,它们将保留在Tomcat中,直到整个Web服务器关闭,这会导致内存泄漏。

自6.0.24版本起,Tomcat检测到此类泄漏并强制注销所有泄漏的驱动。然而,它仍然会通知我们这个问题,这对于我们在不支持此功能的其他Web服务器上重新部署同一应用非常有用。

3. 根本原因与潜在问题

问题的根本原因在于JDBC驱动的不当实现。它应该监听应用程序卸载事件并自行注销。

当Java SPI加载JDBC驱动时,它使用当前上下文的类加载器加载。由于驱动位于应用的WEB-INF/lib中,SPI使用其类加载器加载。这种方式加载的驱动会被注册到DriverManager类(一个JVM单例)中。如果没有这样做,将会引入加载类的内存泄漏。

当我们卸载web应用时,其类加载器会被垃圾回收。然而,DriverManager仍然引用JDBC驱动,阻止了垃圾回收。如果我们再次部署相同的web应用,会创建一个新的类加载器,SPI会再次加载相同的JDBC驱动,从而形成内存泄漏。

4. 缓解措施

有多种方法可以缓解这个问题。

4.1. 使用更新的Tomcat版本

自6.0.24版本起,Tomcat会自动为我们处理这个问题。这意味着我们可以安全地忽略警告消息。

4.2. 手动在退出回调时注销

我们可以在应用的任何退出回调中手动注销驱动。在标准情况下,如果我们的应用只加载了一个JDBC驱动,只需一行代码即可完成:

DriverManager.deregisterDriver(DriverManager.getDrivers().nextElement());

需要注意的是,虽然Tomcat称为“注销”,但实际调用的是DriverManager的deregistration方法。

4.3. 移动JDBC JAR文件

官方处理方式是将JDBC驱动的JAR文件从应用的WEB-INF/lib移动到Tomcat的lib目录。因为lib目录下的所有JAR文件也在类路径中,Tomcat仍然会自动加载驱动,但使用自己的类加载器加载。

当我们部署应用时,Tomcat不会加载任何驱动实现,因为WEB-INF/lib下不会有。这意味着我们可以安全地卸载并重新部署,而不会加载新的内容,从而防止任何泄漏。

5. 总结

在这篇文章中,我们详细讨论了Tomcat关于JDBC驱动强制注销警告消息的含义,以及其根源和解决方法。