programing

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

projobs 2022. 8. 10. 23:52
반응형

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

웹 어플리케이션을 실행할 때 이 메시지가 나타납니다.정상적으로 동작하고 있습니다만, 셧다운시에 이 메세지가 표시됩니다.

중요: 웹 어플리케이션이 JBDC 드라이버[oracle.jdbc.driver]를 등록했습니다.OracleDriver]는 웹 어플리케이션이 정지되었을 때 등록 해제에 실패했습니다.메모리 누수를 방지하기 위해 JDBC 드라이버가 강제로 등록 해제되었습니다.

아무쪼록 잘 부탁드립니다.

버전 6.0.24 이후 Tomcat에는 메모리 누전 검출 기능이 탑재되어 있어 웹 앱에 JDBC 4.0 호환 드라이버가 있는 경우 이러한 경고 메시지가 표시될 수 있습니다./WEB-INF/libAPI를 사용하여 웹 앱 시작 시 자동으로 등록되지만 웹 앱 종료 시 자동으로 등록 취소되지 않습니다.이 메시지는 단순한 비공식 메시지이므로 Tomcat은 이미 메모리 누전 방지 조치를 취했습니다.

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

  1. 츠키노Tomcat tomcat tomcat tomcat tomcat tomcat tomcat tomcat tomcat tomcat tomcat 。실제 버그는 사용자의 코드가 아닌 다른 사용자의 코드(해당 JDBC 드라이버)에 있습니다.Tomcat이 올바르게 작업을 수행했다는 점에 만족하고 JDBC 드라이버 벤더가 드라이버를 업그레이드할 수 있도록 수정할 때까지 기다립니다.「Webapps」 「JDBC」 「Webapps」 「Webapps」에 하면 안 됩니다./WEB-INF/lib의 , , , , , , , in in in in , 。/lib하고 있는/WEB-INF/libServletContextListener.

  2. 이러한 경고에 시달리지 않도록 Tomcat 6.0.23 이전 버전으로 다운그레이드하십시오.하지만 묵묵히 메모리 누설이 계속됩니다.알고 있는 게 좋은 건지는 모르겠지만이러한 종류의 메모리 누수는 Tomcat 핫 배포 시 발생하는 문제의 주요 원인 중 하나입니다.

  3. 의 JDBC "Tomcat"으로 합니다./lib연결 풀링된 데이터 소스를 사용하여 드라이버를 관리합니다.Tomcat의 내장 DBCP는 닫았을 때 드라이버를 제대로 등록 해제하지 않습니다.또, 「UNTFIX」로서 닫혀 있는 DBCP-322 의 버그도 참조해 주세요.DBCP를 DBCP보다 더 잘 동작하는 다른 접속 풀로 대체하는 것이 좋습니다.예를 들어 HikariCPTomcat 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()메서드는 웹 앱 컨텍스트의 ClassLoader가 아닌 상위 ClassLoader(서블릿 컨테이너의 클래스 로더)에 의해 로드되었을 수 있습니다(예: 웹 앱이 아닌 컨테이너의 lib 폴더에 있을 수 있으므로 전체 컨테이너에서 공유됩니다).이러한 등록 해제는 이러한 애플리케이션을 사용할 수 있는 다른 웹 앱(또는 컨테이너 자체)에 영향을 미칩니다.

따라서 각 드라이버의 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);
        }
    }
}

나는 이 문제가 자주 발생하는 것을 본다.네, Tomcat 7은 자동으로 등록 해제를 합니다만, 정말로 코드를 제어하고, 좋은 코딩 방법을 사용할 수 있을까요?모든 개체를 닫고 데이터베이스 연결 풀 스레드를 종료하며 모든 경고를 제거할 수 있는 올바른 코드가 모두 갖추어져 있는지 확인해야 합니다.난 분명히 한다.

이렇게 하는 거예요.

순서 1: 청취자 등록

web.xml

<listener>
    <listener-class>com.mysite.MySpecialListener</listener-class>
</listener>

순서 2: 청취자 구현

com.my site.My Special Listener.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에 의해 직접 로드됨), 메시지가 사라집니다.따라서 mysql jdbc 드라이버는 JVM 셧다운 시에만 언로드되며 메모리 누수는 아무도 신경 쓰지 않습니다.

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>

애플리케이션별 도입을 위한 솔루션

이것은 문제를 해결하기 위해 쓴 청취자입니다.드라이버가 스스로 등록되어 있으면 자동 검출되어 accordingly.it 를 실행합니다.

중요: 드라이버 jar가 Tomcat /lib이 아닌 WEB-INF/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");
        }

    }

}

여기에 스프링 포럼에서 발견한 내용을 추가하겠습니다.JDBC 드라이버 jar를 webapp과 함께 배포하는 대신 tomcat lib 폴더로 이동하면 경고가 사라집니다.이것이 나에게 효과가 있었음을 확인할 수 있다.

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

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 Heap Space 오류가 발생하여 컨텍스트가 완전히 재충전되지 않았습니다.

Apache Tomcat 6.0.29 및 JDK 6u12 버전입니다.

URL http://wiki.apache.org/tomcat/MemoryLeakProtection 의 「References」섹션에서 권장하는 대로 JDK 를 6u21 로 업그레이드하면, Java 힙 스페이스의 문제가 해결됩니다(이 문제는 정상적으로 새로고침 됩니다). 단, JDBC 드라이버 에러는 아직 표시됩니다.

Tomcat 버전 6.026에서도 같은 문제가 발견되었습니다.

WebAPP 라이브러리와 Tomcat Lib에서 Mysql JDBC.jar를 사용했습니다.

Tomcat lib 폴더에서 Jar를 제거하여 위의 문제를 해결합니다.

따라서 Tomcat은 JDBC 메모리 누수를 적절하게 처리하고 있는 것으로 알고 있습니다.그러나 MYSQL Jdbc jar가 WebApp과 Tomcat Lib에 중복되어 있는 경우 Tomcat은 Tomcat Lib 폴더에 있는 jar만 처리할 수 있습니다.

저는 Grails 어플리케이션을 AWS에 전개할 때 이 문제에 직면했습니다.이것은 JDBC 기본 드라이버 org.h2 드라이버의 문제입니다.설정 폴더 내의 Datasource.groovy에서 확인할 수 있듯이 다음과 같습니다.

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

org.h2가 언급되어 있는 모든 행에 코멘트를 붙입니다.datasource.groovy 파일의 드라이버(데이터 소스를 사용하지 않는 경우).그렇지 않으면 데이터베이스 jar 파일을 다운로드해야 합니다.

고마워요.

이 에러는 JTDS 드라이버 1.3.0(SQL Server)을 사용하는 Grails 어플리케이션에서 발생했습니다.SQL Server에서의 잘못된 로그인이 문제였습니다.SQL Server에서 이 문제를 해결한 후 Tomcat에서 앱이 올바르게 배포되었습니다.힌트: stacktrace.log에서 오류를 확인했습니다.

언급URL : https://stackoverflow.com/questions/3320400/to-prevent-a-memory-leak-the-jdbc-driver-has-been-forcibly-unregistered

반응형