回答:
バージョン6.0.24以降、Tomcatにはメモリリーク検出機能が同梱されており、APIを使用してWebアプリケーションの起動時に/WEB-INF/lib
自動登録するJDBC 4.0互換ドライバーがWebアプリケーションにある場合、この種の警告メッセージが表示される可能性があります。webappのシャットダウン中に自動登録解除されませんでした。このメッセージは純粋に非公式であり、Tomcatはすでにそれに応じてメモリリーク防止アクションを実行しています。ServiceLoader
あなたは何ができますか?
これらの警告は無視してください。Tomcatは正しく機能しています。実際のバグは、あなたのコードではなく、他の誰かのコード(問題のJDBCドライバー)にあります。Tomcatが正しく機能し、JDBCドライバーのベンダーが修正してドライバーをアップグレードできるようになるまで待ちます。一方、JDBCドライバーをwebappにドロップするの/WEB-INF/lib
ではなく、サーバーにドロップする必要があります/lib
。それをwebappに保持している場合は/WEB-INF/lib
、を使用して手動で登録および登録解除する必要がありServletContextListener
ます。
これらの警告に煩わされないように、Tomcat 6.0.23以前にダウングレードしてください。しかし、それは静かにメモリリークを続けます。結局それを知っておくのが良いかどうかわからない。これらの種類のメモリリークは、Tomcatのホットデプロイメント時のOutOfMemoryError
問題の背後にある主要な原因の1つです。
JDBCドライバーをTomcatの/lib
フォルダーに移動し、ドライバーを管理するための接続プールデータソースを用意します。Tomcatの組み込みDBCPは、終了時にドライバを適切に登録解除しないことに注意してください。WONTFIXとしてクローズされているバグDBCP-322も参照してください。DBCPの代わりに、DBCPよりも機能が優れている別の接続プールにDBCPを置き換えたいと思います。たとえば、HikariCP、BoneCP、またはTomcat JDBCプール。
lib
ます。
サーブレットコンテキストリスナーの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が行うメモリリーク防止チェックを実行しない別のサーブレットコンテナーに移動する場合に備えて、コンテキスト破棄時にWebアプリケーションによって作成されたすべてのリソースをクリーンアップすることをお勧めします。
ただし、ブランケットドライバーの登録解除の方法論は危険です。DriverManager.getDrivers()
メソッド によって返された一部のドライバーは、webappコンテキストのClassLoaderではなく、親ClassLoader(つまり、サーブレットコンテナーのクラスローダー)によって読み込まれた可能性があります(たとえば、webappではなくコンテナーのlibフォルダーにあるため、コンテナー全体で共有されている可能性があります) )。これらの登録を解除すると、それらを使用している可能性のある他の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);
}
}
}
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
ますか?なぜあなたはどこ、ステップ2を実行する前に、手順1を行うのですかinitContext
、envContext
とdatasource
すべてで参照されていませんか?私はステップ3を理解していないので、私は聞いてるのよ
lookup
オブジェクトを取得するだけのようです。ステップ3.はまったく役に立たない。これは確かに安全性を追加するものではなく、GCの仕組みを理解していない初心者が行うようなものです。私は単にstackoverflow.com/a/5315467/897024を使用して、すべてのドライバーを削除します。
これは、mysqlのドライバまたはtomcats webapp-classloaderの純粋なドライバ登録/登録解除の問題です。mysqlドライバーをtomcats libフォルダーにコピーすると(tomcatによってではなく、jvmによって直接ロードされます)、メッセージは表示されなくなります。これにより、JVMのシャットダウン時にのみmysql jdbcドライバがアンロードされ、メモリリークについては誰も気にしません。
これは、問題を解決するために私が書いたリスナーです。ドライバーが自分自身を登録したかどうかを自動検出し、それに応じて動作します。
重要:各アプリケーションが独自のドライバーを処理して、手つかずのTomcatで実行できるように、ドライバーjarが Tomcat / libではなくWEB-INF / libにデプロイされている場合にのみ使用することを意図しています。それは私見であるべき方法です。
リスナーを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をwebappでデプロイする代わりに、tomcat libフォルダーに移動すると、警告が消えるようです。これでうまくいったことを確認できます
JDBCドライバーの登録を解除する単純なdestroy()メソッドの実装がうまく機能していることがわかりました。
/**
* 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>
このバグ修正のきっかけとなったソース。
同様の問題がありましたが、Tomcatサーバーが実行されている状態でJSPページを変更または保存すると、Javaヒープスペースエラーが発生したため、コンテキストが完全に再充電されませんでした。
私のバージョンはApache Tomcat 6.0.29とJDK 6u12でした。
URL http://wiki.apache.org/tomcat/MemoryLeakProtectionの参照セクションで提案されているようにJDKを6u21にアップグレードすると、JDBCドライバーエラーが表示されますが、Javaヒープスペースの問題が解決されました(コンテキストはリロードできます)。
GrailsアプリケーションをAWSにデプロイしているときにこの問題に直面しました。これは、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ファイルをダウンロードする必要があります。
感謝します。