Answers:
从6.0.24版开始,Tomcat附带了内存泄漏检测功能,当webapp中存在与JDBC 4.0兼容的驱动程序时,该漏洞又会导致这种警告消息,该驱动程序会在使用API启动webapp时/WEB-INF/lib
自动进行注册,但是在webapp关闭期间未自动注销自身。该消息纯粹是非正式的,Tomcat已经采取了相应的内存泄漏预防措施。ServiceLoader
你能做什么?
忽略那些警告。Tomcat正在正确地执行其工作。实际的错误是在别人的代码(有问题的JDBC驱动程序)中,而不是您的代码中。对Tomcat能够正确完成工作感到高兴,并等待JDBC驱动程序供应商对其进行修复,以便您可以升级驱动程序。另一方面,您不应在webapp的JDBC驱动程序中删除JDBC驱动程序/WEB-INF/lib
,而只能在server的JDBC驱动程序中删除JDBC驱动程序/lib
。如果您仍将其保留在webapp的目录中/WEB-INF/lib
,则应使用进行手动注册和注销ServletContextListener
。
降级到Tomcat 6.0.23或更早的版本,这样您就不会被那些警告所困扰。但是它会默默地保持内存泄漏。毕竟不确定这是否很好。这些内存泄漏是Tomcat热部署期间OutOfMemoryError
问题背后的主要原因之一。
将JDBC驱动程序移动到Tomcat的/lib
文件夹,并具有连接池数据源来管理驱动程序。请注意,Tomcat的内置DBCP在关闭时无法正确注销驱动程序。另请参见错误DBCP-322,该错误已作为WONTFIX关闭。您想用另一个连接池代替DBCP,该连接池比DBCP做得更好。例如HikariCP,BoneCP或Tomcat JDBC Pool。
lib
)。
在您的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);
}
}
尽管Tomcat确实为您强制注销了JDBC驱动程序,但是,如果您移至另一个不执行Tomcat的内存泄漏预防检查的servlet容器,则清理上下文上下文破坏时由webapp创建的所有资源仍然是一个好习惯。
但是,全面取消驾驶员注册的方法很危险。 该DriverManager.getDrivers()
方法返回的某些驱动程序可能已经由父ClassLoader(即servlet容器的classloader)加载,而不是由Webapp上下文的ClassLoader加载(例如,它们可能位于容器的lib文件夹中,而不是Webapp的文件夹中,因此在整个容器中共享) )。注销它们会影响可能正在使用它们的任何其他Web应用程序(甚至容器本身)。
因此,在注销之前,应检查每个驱动程序的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);
}
}
}
if (cl.equals(driver.getClass().getClassLoader())) {
吗?
DriverManager
。我留在一个更详细的评论github.com/spring-projects/spring-boot/issues/...
我看到这个问题很多。是的,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;
}
}
请随时发表评论和/或添加...
DataSource
没有一个close
方法
contextDestroyed
?为什么要做步骤1在做第2步,在之前initContext
,envContext
并datasource
没有在所有被引用的?我问是因为我不懂第3步
lookup
似乎只是获取了一些不需要的对象。步骤3.完全没有用。它当然不会增加任何安全性,并且看起来像初学者会做一些不了解GC如何工作的事情。我只需要使用stackoverflow.com/a/5315467/897024并删除所有驱动程序。
这纯粹是mysql的驱动程序或tomcats webapp-classloader中的驱动程序注册/注销问题。将mysql驱动程序复制到tomcats的lib文件夹中(因此它直接由jvm加载,而不是由tomcat加载),消息将消失。这使得mysql jdbc驱动程序仅在JVM关闭时被卸载,然后再没有人关心内存泄漏。
这是我为解决该问题而编写的侦听器:它自动检测驱动程序是否已注册自身并采取相应措施。
重要提示:它仅应在驱动程序jar部署在WEB-INF / lib中而不是在Tomcat / lib中部署时使用,正如许多人建议的那样,以便每个应用程序都可以照顾自己的驱动程序并在未改动的Tomcat上运行。那就是应该恕我直言的方式。
只需先在web.xml中配置侦听器即可享受。
在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: %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");
}
}
}
@WebListener
并省略web.xml
配置。
我将在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");
}
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>
来源启发我对这个Bug修正。
我遇到了类似的问题,但是此外,每当我在运行Tomcat服务器的情况下修改/保存JSP页面时,都会收到Java Heap Space错误,因此上下文没有完全充电。
我的版本是Apache Tomcat 6.0.29和JDK 6u12。
按照URL http://wiki.apache.org/tomcat/MemoryLeakProtection的“ 参考”部分的建议将JDK升级到6u21解决了Java堆空间问题(现在重新加载OK),尽管JDBC Driver错误仍然出现。
当我在AWS上部署Grails应用程序时遇到了这个问题。这是JDBC默认驱动程序org.h2驱动程序的问题。正如您在配置文件夹中的Datasource.groovy中看到的那样。如您所见:
dataSource {
pooled = true
jmxExport = true
driverClassName = "org.h2.Driver" // make this one comment
username = "sa"
password = ""
}
如果您不使用该数据库,请在datasource.groovy文件中提到org.h2.Driver的任何地方注释这些行。否则,您必须下载该数据库jar文件。
谢谢 。