[java] 메모리 누수를 방지하기 위해 JDBC 드라이버가 강제로 등록 해제되었습니다

웹 응용 프로그램을 실행할 때이 메시지가 나타납니다. 정상적으로 실행되지만 종료하는 동안이 메시지가 나타납니다.

심각 : 웹 응용 프로그램이 JBDC 드라이버 [oracle.jdbc.driver.OracleDriver]를 등록했지만 웹 응용 프로그램이 중지되었을 때 등록을 해제하지 못했습니다. 메모리 누수를 방지하기 위해 JDBC 드라이버가 강제로 등록 해제되었습니다.

도움을 주셔서 감사합니다.



답변

버전 6.0.24 때문에,와 톰캣 선박 메모리 누수 탐지 웹 애플리케이션의에서 JDBC 4.0 호환 드라이버가있을 때 다시 경고 메시지의 종류로 이어질 수 기능을 /WEB-INF/lib하는 자동 등록 자체가 사용하는 웹 애플리케이션의 시작시 ServiceLoaderAPI를 하지만, 어떤 webapp 종료 중에 자동 등록 취소 하지 않았습니다 . 이 메시지는 순수한 비공식적이며 Tomcat은 이미 메모리 누수 방지 조치를 취했습니다.

당신은 무엇을 할 수 있나요?

  1. 이러한 경고는 무시하십시오. Tomcat이 올바르게 작업하고 있습니다. 실제 버그는 귀하의 코드가 아닌 다른 사람의 코드 (문제의 JDBC 드라이버)에 있습니다. Tomcat이 작업을 올바르게 수행하고 JDBC 드라이버 공급 업체가 수정 될 때까지 기다렸다가 드라이버를 업그레이드 할 수있게하십시오. 반면에, 당신은 webapp ‘s에서 JDBC 드라이버를 삭제하지 말고 /WEB-INF/libserver ‘s에서만 삭제해야합니다 /lib. 여전히 webapp ‘s에 보관하는 경우을 /WEB-INF/lib사용하여 수동으로 등록 및 등록 취소해야합니다 ServletContextListener.

  2. 이러한 경고가 발생하지 않도록 Tomcat 6.0.23 이상으로 다운 그레이드하십시오. 그러나 자동으로 메모리 누수를 유지합니다. 결국 그것이 좋은지 확실하지 않습니다. 메모리 누수의 이러한 종류의 뒤에 주요 원인 중 하나 OutOfMemoryError문제 톰캣 hotdeployments 동안.

  3. JDBC 드라이버를 Tomcat /lib폴더 로 이동하고 드라이버 를 관리하기 위해 연결 풀링 된 데이터 소스를 갖습니다. Tomcat의 내장 DBCP는 닫을 때 드라이버를 올바르게 등록 취소하지 않습니다. WONTFIX로 닫힌 버그 DBCP-322 도 참조하십시오 . 오히려 DBCP를 DBCP보다 더 잘 수행하는 다른 연결 풀로 교체하고 싶습니다. 예를 들어, HikariCP , BoneCP 또는 Tomcat JDBC Pool이 있습니다.


답변

서블릿 컨텍스트 리스너 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);
        }
    }
}


답변

이 문제가 많이 발생합니다. 예, 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;
    }

}

의견을 말하거나 추가하십시오 …


답변

이것은 순수하게 mysql의 드라이버 또는 tomcats webapp-classloader의 드라이버 등록 / 파괴 문제입니다. MySQL 드라이버를 tomcats lib 폴더에 복사하십시오 (따라서 Tomcat이 아닌 jvm에 의해 직접로드 됨) 메시지가 사라집니다. 그러면 JVM 종료시에만 mysql jdbc 드라이버가 언로드되고 메모리 누수에 대해서는 신경 쓰지 않습니다.


답변

Maven 빌드 전쟁에서이 메시지를 받으면 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에서 실행할 수 있도록합니다. . 그것이 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");
        }

    }

}