Чтобы предотвратить утечку памяти, драйвер JDBC был принудительно незарегистрирован - PullRequest
310 голосов
/ 23 июля 2010

Я получаю это сообщение при запуске веб-приложения.Он работает нормально, но я получаю это сообщение во время завершения работы.

SEVERE: веб-приложение зарегистрировало драйвер JBDC [oracle.jdbc.driver.OracleDriver], но не удалось отменить его регистрацию при остановке веб-приложения.Чтобы предотвратить утечку памяти, драйвер JDBC был принудительно незарегистрирован.

Любая помощь приветствуется.

Ответы [ 14 ]

291 голосов
/ 23 июля 2010

Начиная с версии 6.0.24, Tomcat поставляется с функцией обнаружения утечки памяти , которая, в свою очередь, может привести к появлению такого рода предупреждающих сообщений, когда в веб-приложении /WEB-INF/lib есть совместимый с JDBC 4.0 драйвер, который автоматически- регистрирует себя во время запуска веб-приложения, используя ServiceLoader API , но которое не автоматически отменяет регистрацию во время завершения работы веб-приложения.Это сообщение является чисто неформальным, Tomcat уже предпринял соответствующие действия по предотвращению утечки памяти.

Что вы можете сделать?

  1. Игнорировать эти предупреждения.Tomcat делает свою работу правильно.Фактическая ошибка в чужом коде (рассматриваемый драйвер JDBC), а не в вашем.Будьте счастливы, что Tomcat выполнил свою работу должным образом, и подождите, пока поставщик драйверов JDBC не исправит это, чтобы вы могли обновить драйвер.С другой стороны, вы не должны удалять драйвер JDBC в /WEB-INF/lib веб-приложения, а только в /lib сервера.Если вы по-прежнему сохраняете его в /WEB-INF/lib веб-приложения, то вам следует вручную зарегистрировать и отменить его регистрацию, используя ServletContextListener.

  2. Снизиться до Tomcat 6.0.23 или более поздней версии, чтобы вы не могливозиться с этими предупреждениями.Но это будет молча продолжать утекать память.Не уверен, хорошо ли это знать в конце концов.Такие утечки памяти являются одной из основных причин OutOfMemoryError проблем во время горячих развертываний Tomcat.

  3. Переместите драйвер JDBC в папку Tomcat /lib иисточник данных пула соединения для управления драйвером.Обратите внимание, что встроенный DBCP Tomcat не отменяет регистрацию драйверов при закрытии.См. Также ошибку DBCP-322 , которая закрывается как WONTFIX.Вы бы предпочли заменить DBCP другим пулом соединений, который выполняет свою работу лучше, чем DBCP.Например, HikariCP , BoneCP или, возможно, Tomcat JDBC Pool .

160 голосов
/ 15 марта 2011

В методе 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);
            }

        }
75 голосов
/ 28 мая 2014

Хотя Tomcat принудительно отменяет регистрацию драйвера JDBC для вас, тем не менее рекомендуется очистить все ресурсы, созданные вашим веб-приложением, при уничтожении контекста, если вы переходите в другой контейнер сервлета, который не выполняет проверки предотвращения утечки памяти, которыеTomcat делает.

Однако методика отмены регистрации драйвера общей оболочки опасна. Некоторые драйверы, возвращаемые методом DriverManager.getDrivers(), возможно, были загружены родительским ClassLoader (т. Е. Загрузчиком классов контейнера сервлета).) не ClassLoader контекста веб-приложения (например, они могут находиться в папке lib контейнера, а не в веб-приложении и, следовательно, совместно использоваться по всему контейнеру).Отмена их регистрации повлияет на любые другие веб-приложения, которые могут их использовать (или даже на сам контейнер).

Поэтому следует проверить, что ClassLoader для каждого драйвера является ClassLoader веб-приложения, прежде чем отменять его регистрацию.Итак, в вашем методе contextDestroyed () вашего ContextListener:

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);
        }
    }
}
25 голосов
/ 19 октября 2011

Я вижу, что эта проблема часто возникает.Да, 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;
    }

}

Пожалуйстане стесняйтесь комментировать и / или добавлять ...

14 голосов
/ 13 сентября 2010

Это чисто проблема регистрации / отмены регистрации драйвера в драйвере mysql или в tomcats webapp-classloader. Скопируйте драйвер mysql в папку lib tomcats (чтобы он загружался непосредственно jvm, а не tomcat), и сообщение исчезнет. Это делает драйвер mysql jdbc выгруженным только при выключении JVM, и никто не заботится об утечках памяти.

8 голосов
/ 04 апреля 2013

Решение для развертываний для приложений

Это слушатель, который я написал для решения проблемы: он автоматически обнаруживает, если драйвер зарегистрировался, и действует соответственно .it

Важно: он предназначен для использования ТОЛЬКО, когда 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");
        }

    }

}
8 голосов
/ 13 сентября 2012

Если вы получаете это сообщение от встроенной войны 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>
6 голосов
/ 28 февраля 2012

Я обнаружил, что реализация простого метода 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");
}
6 голосов
/ 05 июля 2011

Я добавлю к этому то, что нашел на форумах Spring.Если вы переместите свой драйвер JDBC-драйвера в папку lib tomcat, а не развернете его с помощью веб-приложения, предупреждение исчезнет.Я могу подтвердить, что это сработало для меня

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

2 голосов
/ 14 сентября 2010

У меня была похожая проблема, но, кроме того, я получал ошибку Java Heap Space каждый раз, когда я изменял / сохранял страницы JSP с запущенным сервером Tomcat, поэтому контекст не был полностью перезагружен.

Моими версиями были Apache Tomcat 6.0.29 и JDK 6u12.

Обновление JDK до 6u21 , как предложено в Ссылки раздел URL http://wiki.apache.org/tomcat/MemoryLeakProtection, решил проблему пространства кучи Java (контекст теперь перезагружается, ОК) хотя ошибка драйвера JDBC по-прежнему появляется.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...