为防止内存泄漏,JDBC驱动程序已被强制注销
当我运行我的Web应用程序时,我收到了这条消息。 它运行良好,但在关机期间收到此消息。
SEVERE:Web应用程序注册了JBDC驱动程序[oracle.jdbc.driver.OracleDriver],但在Web应用程序停止时未能取消注册。 为防止内存泄漏,JDBC驱动程序已被强制注销。
任何帮助赞赏。
从版本6.0.24开始,Tomcat附带了一个内存泄漏检测function,当webapp的/WEB-INF/lib
中有一个JDBC 4.0兼容的驱动程序时,它会导致这种警告消息,这个驱动程序会在webapp启动时自动注册使用ServiceLoader
API ,但在webappclosures期间没有自动注销 。 这个消息纯属非正式的,Tomcat已经相应地采取了内存泄漏预防行动。
你能做什么?
-
忽略这些警告。 Tomcat正在做正确的工作。 实际的错误是在别人的代码中(所讨论的JDBC驱动程序),而不是在你的代码中。 很高兴Tomcat正常工作,并等待JDBC驱动程序供应商解决问题,以便升级驱动程序。 另一方面,您不应该在webapp的
/WEB-INF/lib
放置JDBC驱动程序,而只能在服务器的/lib
放置JDBC驱动程序。 如果你仍然保存在webapp的/WEB-INF/lib
,那么你应该使用ServletContextListener
手动注册和注销它。 -
降级到Tomcat 6.0.23或更高版本,以免受到这些警告的困扰。 但它会默默地不断泄漏的记忆。 不知道这毕竟是好事。 这些内存泄漏是Tomcat热部署期间
OutOfMemoryError
问题背后的主要原因之一。 -
将JDBC驱动程序移到Tomcat的
/lib
文件夹中,并使用连接池数据源来pipe理驱动程序。 请注意,Tomcat的内置DBCP在closures时不会正确注销驱动程序。 另请参阅作为WONTFIXclosures的错误DBCP-322 。 您宁愿将DBCPreplace为另一个更好的DBCP连接池。 例如HikariCP , BoneCP或Tomcat JDBC池 。
在您的servlet上下文listener 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); } }
尽pipeTomcat为您强制注销了JDBC驱动程序,但是,如果您移动到另一个不执行Tomcat内存泄漏防护检查的servlet容器,则清理由Web应用程序创build的上下文销毁所创build的所有资源是一个好习惯。
然而,全面的司机注销方法是危险的。 由DriverManager.getDrivers()
方法返回的一些驱动程序可能已经由父ClassLoader(即,Servlet容器的类加载器)而不是webapp上下文的ClassLoader加载(例如,它们可能位于容器的lib文件夹中,而不是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); } } }
我看到这个问题出现了很多。 是的,Tomcat 7会自动取消注册,但它是否真正控制了您的代码和良好的编码习惯? 当然,你想知道你有所有正确的代码来closures所有的对象,closures数据库连接池线程,并摆脱所有的警告。 我当然这样做。
这是我如何做到的。
步骤1:注册一个监听器
web.xml中
<listener> <listener-class>com.mysite.MySpecialListener</listener-class> </listener>
步骤2:实现监听器
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; } }
请随意评论和/或添加…
这是纯粹的驱动程序注册/注销问题在MySQL的驱动程序或tomcat的Web应用程序类加载器。 将mysql驱动复制到tomcats lib文件夹(所以它由jvm直接加载,而不是由tomcat加载),并且消息将会消失。 这使得mysql jdbc驱动只能在JVMclosures时被卸载,而没有人关心内存泄漏。
针对每个应用部署的解决scheme
这是我为解决这个问题而写的一个听众:如果司机已经注册了自己并采取相应的行动,它就会自动检测
重要提示: 仅当驱动程序jar被部署在WEB-INF / lib中 ,而不是在Tomcat / lib中时才使用,因为每个应用程序都可以照顾自己的驱动程序并运行在一个未触发的Tomcat上。 这应该是恕我直言的方式。
只需在您的web.xml中configuration监听器,然后再享用。
在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: %sv%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"); } } }
如果你从一个Maven构build的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>
我将在Spring论坛上添加一些我发现的东西。 如果您将JDBC驱动程序jar移动到tomcat lib文件夹,而不是将其部署到您的webapp,则警告似乎消失。 我可以证实这对我有效
我发现实现一个简单的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"); }
我有一个类似的问题,但另外我得到一个Java堆空间错误,随时修改/保存JSP页面运行Tomcat服务器,因此上下文没有完全充电。
我的版本是Apache Tomcat 6.0.29和JDK 6u12。
根据URL http://wiki.apache.org/tomcat/MemoryLeakProtection中的;参考资料部分的build议,将JDK升级到6u21解决了Java堆空间问题(上下文现在重新载入正常),尽pipeJDBC驱动程序错误仍然出现。
我发现与Tomcat版本6.026相同的问题。
我使用WebAPP库中的Mysql JDBC.jar以及TOMCAT Lib。
要通过从TOMCAT lib文件夹中删除Jar来修复上述问题。
所以我知道TOMCAT正在处理JDBC内存泄漏。 但是,如果在WebApp和Tomcat Lib中重复使用MYSQL Jdbc jar,则Tomcat将只能处理Tomcat Lib文件夹中存在的jar。
在AWS上部署我的Grails应用程序时,我遇到了这个问题。 这是JDBC默认驱动程序org.h2驱动程序的问题。 正如你可以在configuration文件夹中的Datasource.groovy中看到的那样。 如下所示:
dataSource { pooled = true jmxExport = true driverClassName = "org.h2.Driver" // make this one comment username = "sa" password = "" }
在datasource.groovy文件中提到org.h2.Driver的地方注释那些行,如果你没有使用那个数据库的话。 否则,你必须下载该数据库的jar文件。
谢谢 。
删除应用程序(tomcat6)解决了这个问题。 conf文件被保存。 它以某种方式破坏自己。 我不确定它是如何做到的。