답변:
버전 6.0.24 때문에,와 톰캣 선박 메모리 누수 탐지 웹 애플리케이션의에서 JDBC 4.0 호환 드라이버가있을 때 다시 경고 메시지의 종류로 이어질 수 기능을 /WEB-INF/lib
하는 자동 등록 자체가 사용하는 웹 애플리케이션의 시작시 ServiceLoader
API를 하지만, 어떤 webapp 종료 중에 자동 등록 취소 하지 않았습니다 . 이 메시지는 순수한 비공식적이며 Tomcat은 이미 메모리 누수 방지 조치를 취했습니다.
당신은 무엇을 할 수 있나요?
이러한 경고는 무시하십시오. Tomcat이 올바르게 작업하고 있습니다. 실제 버그는 귀하의 코드가 아닌 다른 사람의 코드 (문제의 JDBC 드라이버)에 있습니다. Tomcat이 작업을 올바르게 수행하고 JDBC 드라이버 공급 업체가 수정 될 때까지 기다렸다가 드라이버를 업그레이드 할 수있게하십시오. 반면에, 당신은 webapp 's에서 JDBC 드라이버를 삭제하지 말고 /WEB-INF/lib
server 's에서만 삭제해야합니다 /lib
. 여전히 webapp 's에 보관하는 경우을 /WEB-INF/lib
사용하여 수동으로 등록 및 등록 취소해야합니다 ServletContextListener
.
이러한 경고가 발생하지 않도록 Tomcat 6.0.23 이상으로 다운 그레이드하십시오. 그러나 자동으로 메모리 누수를 유지합니다. 결국 그것이 좋은지 확실하지 않습니다. 메모리 누수의 이러한 종류의 뒤에 주요 원인 중 하나 OutOfMemoryError
문제 톰캣 hotdeployments 동안.
JDBC 드라이버를 Tomcat /lib
폴더 로 이동하고 드라이버 를 관리하기 위해 연결 풀링 된 데이터 소스를 갖습니다. Tomcat의 내장 DBCP는 닫을 때 드라이버를 올바르게 등록 취소하지 않습니다. WONTFIX로 닫힌 버그 DBCP-322 도 참조하십시오 . 오히려 DBCP를 DBCP보다 더 잘 수행하는 다른 연결 풀로 교체하고 싶습니다. 예를 들어, HikariCP , BoneCP 또는 Tomcat JDBC Pool이 있습니다.
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이 수행하는 메모리 누수 방지 검사를 수행하지 않는 다른 서블릿 컨테이너로 이동할 경우 컨텍스트 삭제시 웹 애플리케이션에서 생성 한 모든 리소스를 정리하는 것이 좋습니다.
그러나 블랭킷 드라이버 등록 취소 방법은 위험합니다. DriverManager.getDrivers()
메소드가 리턴 한 일부 드라이버 는 웹 애플리케이션 컨텍스트의 클래스 로더가 아닌 상위 클래스 로더 (예 : 서블릿 컨테이너의 클래스 로더)에 의해로드되었을 수 있습니다 (예 : 웹 애플리케이션이 아닌 컨테이너의 lib 폴더에있을 수 있으므로 전체 컨테이너에서 공유 됨) ). 이들을 등록 취소하면 사용중인 다른 웹앱 (또는 컨테이너 자체)에 영향을줍니다.
따라서 등록을 해제하기 전에 각 드라이버의 ClassLoader가 웹앱의 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 드라이버가 언로드되고 메모리 누수에 대해서는 신경 쓰지 않습니다.
이것은 문제를 해결하기 위해 작성한 리스너입니다. 드라이버가 자신을 등록했는지 여부를 자동 감지하고 그에 따라 행동합니다.
중요 : 드라이버 jar이 WEB-INF / lib에 배포 된 경우에만 사용하도록되어 있습니다 Tomcat / lib가 아닌 합니다. 각 응용 프로그램이 자체 드라이버를 처리하고 손대지 않은 Tomcat에서 실행할 수 있도록합니다. . 그것이 IMHO가되어야하는 방식입니다.
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 폴더로 이동하면 경고가 사라지는 것 같습니다. 이것이 나를 위해 일한 것을 확인할 수 있습니다
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 파일을 다운로드해야합니다.
감사 .