为了防止内存泄漏,JDBC驱动程序已被强制注销

当我运行我的web应用程序时,我得到这条消息。它运行良好,但我在关机期间收到这条消息。

严重:web应用程序注册了JBDC驱动程序[oracle.jdbc.driver. exe]。但是当web应用程序停止时,无法注销它。为了防止内存泄漏,JDBC驱动程序已被强制注销。

感谢任何帮助。

418355 次浏览

从版本6.0.24开始,Tomcat附带了一个内存泄漏检测特性,当webapp的/WEB-INF/lib中有一个JDBC 4.0兼容的驱动程序时,它会在webapp使用ServiceLoader API启动时自动-寄存器,但在webapp关闭时不自动-取消时,会导致这种警告消息。此消息完全是非正式的,Tomcat已经采取了相应的内存泄漏预防操作。

你能做什么?

  1. 忽略这些警告。Tomcat正在正确地完成它的工作。实际的错误在别人的代码中(JDBC驱动程序),而不是在你的代码中。很高兴Tomcat正确地完成了它的工作,并等待JDBC驱动程序供应商修复它,以便您可以升级驱动程序。另一方面,你不应该在webapp的/WEB-INF/lib中删除JDBC驱动程序,而只应该在服务器的/lib中删除。如果你仍然将它保存在webapp的/WEB-INF/lib中,那么你应该使用ServletContextListener手动注册和注销它。

  2. 降级到Tomcat 6.0.23或更老版本,这样您就不会受到这些警告的困扰。但它会默默地继续泄漏内存。我不确定知道这些是不是好事。这些类型的内存泄漏是Tomcat热部署期间OutOfMemoryError问题背后的主要原因之一。

  3. 将JDBC驱动程序移动到Tomcat的/lib文件夹,并有一个连接池数据源来管理驱动程序。注意Tomcat的内置DBCP在关闭时不会正确注销驱动程序。请参见bug dbcp - 322,它被关闭为WONTFIX。您更希望用另一个连接池替换DBCP,该连接池比DBCP做得更好。例如HikariCPTomcat JDBC池

这纯粹是mysql的驱动程序或tomcats webapp-classloader中的驱动程序注册/注销问题。复制mysql驱动到tomcats lib文件夹(所以它是由jvm直接加载,而不是由tomcat),消息将会消失。这使得mysql jdbc驱动程序只有在JVM关闭时才会被卸载,没有人会关心内存泄漏。

我也遇到了类似的问题,但除此之外,每当我在运行Tomcat服务器的情况下修改/保存JSP页面时,我都会得到一个Java堆空间错误,因此上下文没有完全充电。

我的版本是Apache Tomcat 6.0.29和JDK 6u12。

根据URL http://wiki.apache.org/tomcat/MemoryLeakProtection引用< em > < / em >部分的建议,将JDK升级到6 u21解决了Java堆空间问题(上下文现在重新加载OK),尽管仍然出现JDBC驱动程序错误。

在你的servlet上下文监听器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);
}
}

我将补充一些我在Spring论坛上发现的东西。如果您将JDBC驱动程序jar移到tomcat lib文件夹,而不是与webapp一起部署它,则警告似乎消失了。我可以确定这对我很有效

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

我经常看到这个问题。是的,Tomcat 7会自动注销它,但这真的能控制你的代码吗?这是一个好的编码实践吗?当然,您希望知道您已经准备好了关闭所有对象、关闭数据库连接池线程和消除所有警告所需的所有正确代码。我当然喜欢。

我就是这么做的。

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


}

请随意评论和/或添加…

我发现实现一个简单的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");
}

我在Tomcat版本6.026中发现了同样的问题。

我在WebAPP库和TOMCAT库中使用Mysql JDBC.jar。

要解决上述问题,请从TOMCAT lib文件夹中删除Jar。

因此,我所理解的是TOMCAT正在正确地处理JDBC内存泄漏。但是如果MYSQL Jdbc jar在WebApp和Tomcat Lib中被复制,Tomcat将只能处理Tomcat Lib文件夹中的jar。

如果您从Maven构建的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>

针对每个应用部署的解决方案

这是一个侦听器,我写来解决这个问题:它自动检测驱动程序是否已经注册自己,并采取相应的行动

重要:它应该被使用在只有当驱动jar部署在WEB-INF/lib中中,而不是像许多人建议的那样在Tomcat /lib中使用,这样每个应用程序都可以处理自己的驱动程序,并在未触及的Tomcat上运行。恕我直言,这才是应该的方式。

只需在web.xml中配置侦听器,然后再执行其他操作即可。

web . xml的顶部添加:

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

保存为跑龙套/ 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");
}


}


}

尽管Tomcat会强制注销JDBC驱动程序,但在上下文破坏时清理webapp创建的所有资源是一个很好的实践,以防您移动到另一个servlet容器,该容器不像Tomcat那样执行内存泄漏预防检查。

DriverManager.getDrivers()方法返回的一些驱动程序可能是由父类加载器(即servlet容器的类加载器)加载的,而不是webapp上下文的类加载器(例如,它们可能在容器的lib文件夹中,而不是webapp的,因此在整个容器中共享)。注销这些将会影响其他可能使用它们的web应用程序(甚至容器本身)。

因此,在取消注册之前,应该检查每个驱动程序的ClassLoader是否是web应用程序的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);
}
}
}
我在AWS上部署Grails应用程序时遇到过这个问题。这是JDBC默认驱动程序org.h2驱动程序的问题。 正如你在数据源中看到的那样。配置文件夹中的Groovy。如下所示:

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

在数据源中提到org.h2.Driver的地方注释那些行。Groovy文件,如果您没有使用该数据库。 否则你必须下载数据库jar文件

谢谢。

要防止这种内存泄漏,只需在上下文关闭时注销驱动程序。

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>

启发了我这个错误修复。

这个错误发生在我使用JTDS Driver 1.3.0 (SQL Server)的Grails应用程序中。问题是SQL Server登录错误。解决这个问题后(在SQL Server),我的应用程序被正确部署在Tomcat。提示:我在stacktrace.log中看到了错误