web-dev-qa-db-fra.com

Pour éviter une fuite de mémoire, le pilote JDBC a été désinscrit de force.

Je reçois ce message lorsque je lance mon application Web. Il fonctionne bien, mais je reçois ce message lors de l’arrêt.

SEVERE: une application Web a enregistré le pilote JBDC [Oracle.jdbc.driver.OracleDriver] mais n'a pas réussi à l'annuler lors de l'arrêt de l'application Web. Pour éviter une fuite de mémoire, le pilote JDBC a été désinscrit de force.

Toute aide appréciée.

296
mona

Depuis la version 6.0.24, Tomcat est livré avec une fonction détection de fuite de mémoire , ce qui peut entraîner ce type de messages d'avertissement lorsqu'il existe un pilote compatible JDBC 4.0 dans la /WEB-INF/lib de la Webapp qui - s'enregistre lui-même lors du démarrage de l'application Web à l'aide de ServiceLoader API , mais ne s'est pas auto-enregistré - désinscription lors de l'arrêt de l'application Webapp. Ce message est purement informel, Tomcat a déjà pris les mesures de prévention des fuites de mémoire en conséquence.

Que pouvez-vous faire?

  1. Ignorer ces avertissements. Tomcat fait bien son travail. Le bogue réel est dans le code de quelqu'un d'autre (le pilote JDBC en question), pas dans le vôtre. Soyez heureux que Tomcat ait fait son travail correctement et attendez que le fournisseur de pilotes JDBC le répare afin de pouvoir mettre à niveau le pilote. D'autre part, vous n'êtes pas censé supprimer un pilote JDBC dans /WEB-INF/lib de la Webapp, mais uniquement dans /lib du serveur. Si vous le conservez toujours dans /WEB-INF/lib de la Webapp, vous devez alors l'enregistrer et le désenregistrer manuellement à l'aide d'un ServletContextListener.

  2. Passez à Tomcat 6.0.23 ou une version antérieure pour ne pas être dérangé par ces avertissements. Mais il gardera silencieusement des fuites de mémoire. Je ne sais pas si c'est bon à savoir après tout. Ce type de fuite de mémoire est l’une des principales causes de problèmes OutOfMemoryError au cours des déploiements à chaud de Tomcat.

  3. Déplacez le pilote JDBC vers le dossier /lib de Tomcat et disposez d'une source de données regroupée en connexions pour gérer le pilote. Notez que le DBCP intégré de Tomcat ne désenregistre pas correctement les pilotes à la fermeture. Voir aussi bug DBCP-322 qui est fermé en tant que WONTFIX. Vous préférez remplacer DBCP par un autre pool de connexions qui fait mieux son travail que DBCP. Par exemple, HikariCP , BoneCP , ou peut-être Tomcat JDBC Pool .

291
BalusC

Dans votre méthode listener contextDestroyed () de la servlet context listert, désenregistrez manuellement les pilotes:

        // 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);
            }

        }
157
ae6rt

Bien que Tomcat supprime de force l'enregistrement du pilote JDBC pour vous, il est néanmoins recommandé de nettoyer toutes les ressources créées par votre application Web lors de la destruction du contexte si vous passez dans un autre conteneur de servlet qui n'effectue pas les contrôles de prévention des fuites de mémoire effectués par Tomcat.

Cependant, la méthodologie de désinscription d'un chauffeur blanchi est dangereuse. Certains pilotes renvoyés par la méthode DriverManager.getDrivers() peuvent avoir été chargés par le ClassLoader parent (c'est-à-dire le chargeur de classe du conteneur de servlet) et non par le ClassLoader du contexte de l'application Web (par exemple, ils peuvent se trouver dans le dossier lib du conteneur, et non par l'application Web, et donc partagés le conteneur entier). Leur désenregistrement affectera toutes les autres applications Web susceptibles de les utiliser (ou même le conteneur lui-même).

Par conséquent, il convient de vérifier que le ClassLoader de chaque pilote est le ClassLoader de l'application Web avant de le désenregistrer. Donc, dans la méthode contextDestroyed () de votre 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);
        }
    }
}
75
megaflop

Je vois beaucoup ce problème. Oui, Tomcat 7 le désenregistre automatiquement, mais est-ce vraiment une prise de contrôle de votre code et une bonne pratique de codage? VOUS voulez sûrement savoir que vous avez tout le code correct en place pour fermer tous vos objets, fermer les threads du pool de connexions de base de données et vous débarrasser de tous les avertissements. Je fais certainement.

C'est comme ça que je le fais.

Étape 1: inscrire un auditeur

web.xml

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

Étape 2: implémenter l'auditeur

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

}

N'hésitez pas à commenter et/ou ajouter ...

25
Spider

Ceci est purement un problème d’inscription/désinscription de pilote dans le pilote de mysql ou dans tomcats webapp-classloader. Copiez le pilote mysql dans le dossier tomcats lib (donc chargé directement par jvm, pas par Tomcat), et le message disparaîtra. Cela fait que le pilote mysql jdbc est déchargé uniquement à l’arrêt de la machine virtuelle Java et que personne ne se soucie alors des fuites de mémoire.

14
sick old bastard

Si vous recevez ce message d'une guerre générée par Maven, changez la portée du pilote JDBC et fournissez-en une copie dans le répertoire lib. Comme ça: 

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

Solution pour les déploiements par application

C'est un auditeur que j'ai écrit pour résoudre le problème: il détecte automatiquement si le pilote s'est enregistré et agit en conséquence. 

Important: il est destiné à être utilisé UNIQUEMENT lorsque le fichier jar du pilote est déployé dans WEB-INF/lib , et non dans Tomcat/lib, comme beaucoup le suggèrent, afin que chaque application puisse gérer son propre pilote sur un Tomcat intact. C'est comme ça que ça devrait être à mon humble avis.

Configurez simplement le programme d'écoute dans votre fichier web.xml avant tout autre et profitez-en.

ajoutez près du sommet de web.xml:

<listener>
    <listener-class>utils.db.OjdbcDriverRegistrationListener</listener-class>    
</listener>

enregistrer sous 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
Andrea Ratto

J'ajouterai à cela quelque chose que j'ai trouvé sur les forums de printemps. Si vous déplacez le fichier JAR du pilote JDBC dans le dossier Tomcat lib, au lieu de le déployer avec votre application Web, l'avertissement semble disparaître. Je peux confirmer que cela a fonctionné pour moi

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

6
Collin Peters

J'ai constaté que l'implémentation d'une simple méthode destroy () pour désenregistrer tout pilote JDBC fonctionnait parfaitement.

/**
 * 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
Darrin M. Gorski

J'avais un problème similaire, mais j'avais également une erreur Java Heap Space chaque fois que je modifiais/sauvegardais des pages JSP avec le serveur Tomcat en cours d'exécution. Par conséquent, le contexte n'était pas complètement rechargé. 

Mes versions étaient Apache Tomcat 6.0.29 et JDK 6u12.

La mise à niveau de JDK vers 6u21 comme suggéré dans la section Références de l'URL http://wiki.Apache.org/Tomcat/MemoryLeakProtection a résolu le problème d'espace de pile Java recharge maintenant OK) bien que l’erreur du pilote JDBC apparaisse toujours.

2
Francisco Alvarado

Pour éviter cette fuite de mémoire, il suffit de désenregistrer le pilote à la fermeture du contexte.

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>

Source qui m'a inspiré pour cette correction de bogue.

1
King-Wizard

J'ai rencontré ce problème lorsque je déployais mon application Grails sur AWS. Cela concerne le pilote par défaut JDBC org.h2 driver. Comme vous pouvez le voir dans le fichier Datasource.groovy dans votre dossier de configuration. Comme vous pouvez le voir ci-dessous: 

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

Commentez ces lignes où il est mentionné org.h2.Driver dans le fichier datasource.groovy, si vous n'utilisez pas cette base de données . Sinon, vous devez télécharger ce fichier jar de base de données.

Merci .

0
PyDevSRS

J'ai trouvé le même problème avec Tomcat version 6.026.

J'ai utilisé le fichier Mysql JDBC.jar dans la bibliothèque WebAPP ainsi que dans Tomcat Lib.

Pour résoudre ce problème, supprimez le fichier Jar du dossier Tomcat lib.

Donc, ce que je comprends, c’est que Tomcat gère correctement la fuite de mémoire JDBC. Toutefois, si le fichier jar MYSQL Jdbc est dupliqué dans WebApp et Tomcat Lib, Tomcat ne pourra gérer que le fichier jar présent dans le dossier Tomcat Lib.

0
Bharat