Чтобы предотвратить утечку памяти, драйвер JDBC был принудительно незарегистрирован

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

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

Любая помощь была оценена.

Ответ 1

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

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

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

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

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

Ответ 2

В вашем контексте контекст-приемник контекста 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);
            }

        }

Ответ 3

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

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

Поэтому следует проверить, что 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 ClassLoader:
    // Get the webapp 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 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 ClassLoader and may be in use elsewhere
            log.trace("Not deregistering JDBC driver {} as it does not belong to this webapp ClassLoader", driver);
        }
    }
}

Ответ 4

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

}

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

Ответ 5

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

Ответ 6

Если вы получаете это сообщение от измененного войн 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>

Ответ 7

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

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

Важно: предполагается, что он используется ТОЛЬКО, когда баннер драйвера развертывается в 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

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

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

Ответ 9

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

Ответ 10

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

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

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

Ответ 11

Я нашел ту же проблему с Tomcat версии 6.026.

Я использовал Mysql JDBC.jar в библиотеке WebAPP, а также в TOMCAT Lib.

Чтобы исправить это, удалите Jar из папки TOMCAT lib.

Итак, я понимаю, что TOMCAT правильно обрабатывает утечку памяти JDBC. Но если JSB JQB JQB дублируется в WebApp и Tomcat Lib, Tomcat сможет обрабатывать только банку в папке Tomcat Lib.

Ответ 12

Я столкнулся с этой проблемой, когда я развертывал приложение 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.Driver в файле datasource.groovy, если вы не используете эту базу данных. В противном случае вам необходимо загрузить файл базы данных базы данных.

Спасибо.

Ответ 13

Чтобы предотвратить утечку памяти, просто отмените регистрацию драйвера при отключении контекста.

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 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>

Источник, который вдохновил меня на исправление этой ошибки.

Ответ 14

Удаление приложения (tomcat6) решает его. Файлы conf сохраняются. Это как-то ломается. Я не уверен, как это происходит.