345 votes

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

Je reçois ce message lorsque je lance mon application web. Elle 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 à le désenregistrer lorsque l'application web a été arrêtée. Pour éviter une fuite de mémoire, le pilote JDBC a été désenregistré de force.

Toute aide est appréciée.

4 votes

Pourrait être un doublon de stackoverflow.com/questions/2604630/…

317voto

BalusC Points 498232

Depuis la version 6.0.24, Tomcat est livré avec une fonction de détection de fuites de mémoire, ce qui peut entraîner ce type de messages d'avertissement lorsqu'il y a un pilote compatible JDBC 4.0 dans le /WEB-INF/lib de l'application web qui s'auto-enregistre pendant le démarrage de l'application web en utilisant l'API ServiceLoader, mais qui ne s'est pas auto-désenregistré pendant l'arrêt de l'application web. Ce message est purement informatif, Tomcat a déjà pris les mesures de prévention des fuites de mémoire en conséquence.

Que pouvez-vous faire?

  1. Ignorez ces avertissements. Tomcat fait bien son travail. Le bug réel se trouve dans le code de quelqu'un d'autre (le pilote JDBC en question), pas dans le vôtre. Soyez heureux que Tomcat a fait son travail correctement et attendez que le fournisseur du pilote JDBC le corrige pour que vous puissiez mettre à jour le pilote. D'autre part, vous ne devriez pas déposer un pilote JDBC dans le /WEB-INF/lib de l'application web, mais seulement dans le /lib du serveur. Si vous le gardez toujours dans le /WEB-INF/lib de l'application web, alors vous devriez l'enregistrer et le désenregistrer manuellement en utilisant un ServletContextListener.

  2. Revenir à Tomcat 6.0.23 ou une version antérieure pour ne pas être dérangé par ces avertissements. Mais cela continuera à causer une fuite de mémoire. Pas sûr que ce soit bon à savoir après tout. Ce type de fuites de mémoire est l'une des principales causes derrière les problèmes d'OutOfMemoryError lors des déploiements à chaud de Tomcat.

  3. Déplacez le pilote JDBC dans le dossier /lib de Tomcat et utilisez une source de données avec pool de connexions pour gérer le pilote. Notez que le DBCP intégré à Tomcat ne désenregistre pas correctement les pilotes à la fermeture. Consultez également le bug DBCP-322 qui est clos en tant que WONTFIX. Vous préféreriez plutôt remplacer DBCP par un autre pool de connexions qui fait mieux son travail que DBCP. Par exemple HikariCP ou peut-être Tomcat JDBC Pool.

27 votes

Ceci est un bon conseil. Ce n'est pas un avertissement de fuite de mémoire, c'est un avertissement que Tomcat a pris une mesure coercitive pour éviter une fuite.

0 votes

@BalusC, nous n'utilisons pas le pool de connexions de Tomcat, pensez-vous que le message devrait quand même s'afficher?

0 votes

Pouvons-nous le laisser tel quel ou suggérez-vous d'utiliser la solution présentée dans DBCP-322 que vous avez fournie dans la réponse? Je me demandais si nous pouvions simplement ignorer ces avertissements! Merci pour vos réponses rapides...

170voto

ae6rt Points 1144

Dans la méthode contextDestroyed() de votre écouteur de contexte servlet, désinscrivez manuellement les pilotes :

// Ceci désinscrit manuellement le pilote JDBC, ce qui empêche Tomcat 7 de se plaindre de fuites de mémoire concernant cette classe
Enumeration drivers = DriverManager.getDrivers();
while (drivers.hasMoreElements()) {
    Driver driver = drivers.nextElement();
    try {
        DriverManager.deregisterDriver(driver);
        LOG.log(Level.INFO, String.format("désinscription du pilote jdbc : %s", driver));
    } catch (SQLException e) {
        LOG.log(Level.SEVERE, String.format("Erreur lors de la désinscription du pilote %s", driver), e);
    }
}

3 votes

Ça marche! javabeat.net/servletcontextlistener-example peut aider à implémenter un écouteur de contexte de servlet

22 votes

Il s'agit potentiellement non sécurisé dans un environnement partagé car vous ne voudriez peut-être pas désenregistrer tous les pilotes JDBC disponibles. Consultez ma réponse pour une approche plus sûre.

96voto

daiscog Points 3088

Bien que Tomcat désenregistre de force le pilote JDBC pour vous, il est néanmoins bon de nettoyer toutes les ressources créées par votre application Web lors de la destruction du contexte au cas où vous passeriez à un autre conteneur de servlet qui ne ferait pas les vérifications de prévention des fuites de mémoire que fait Tomcat.

Cependant, la méthodologie de désenregistrement global du pilote est dangereuse. Certains pilotes retournés par la méthode DriverManager.getDrivers() ont peut-être été chargés par le Parent ClassLoader (c'est-à-dire, le classloader du conteneur de servlet) et non par le ClassLoader du contexte de l'application Web (par exemple, ils peuvent être dans le dossier lib du conteneur, pas de l'application Web, et donc partagés dans tout le conteneur). Désenregistrer ceux-ci affectera toute autre application Web qui pourrait 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) {
    // ... Tout d'abord, fermez toutes les tâches de fond qui pourraient utiliser la BD ...
    // ... Puis fermez tous les pools de connexions à la BD ...

    // Maintenant, désenregistrez les pilotes JDBC dans le ClassLoader de ce contexte :
    // Obtenez le ClassLoader de l'application Web
    ClassLoader cl = Thread.currentThread().getContextClassLoader();
    // Parcourir tous les pilotes
    Enumeration drivers = DriverManager.getDrivers();
    while (drivers.hasMoreElements()) {
        Driver driver = drivers.nextElement();
        if (driver.getClass().getClassLoader() == cl) {
            // Ce pilote a été enregistré par le ClassLoader de l'application Web, donc désenregistrez-le :
            try {
                log.info("Désenregistrement du pilote JDBC {}", driver);
                DriverManager.deregisterDriver(driver);
            } catch (SQLException ex) {
                log.error("Erreur lors du désenregistrement du pilote JDBC {}", driver, ex);
            }
        } else {
            // Le pilote n'a pas été enregistré par le ClassLoader de l'application Web et peut être utilisé ailleurs
            log.trace("Ne désenregistrez pas le pilote JDBC {} car il n'appartient pas au ClassLoader de cette application Web", driver);
        }
    }
}

0 votes

Ne devrait-il pas être if (cl.equals(driver.getClass().getClassLoader())) { ?

7 votes

@user11153 Non, nous vérifions s'il s'agit précisément de la même instance de ClassLoader, et non de deux instances distinctes qui sont égales en valeur.

6 votes

Bien que les autres semblent gérer correctement le problème en question, ils posent des problèmes lorsque le fichier de guerre est supprimé puis remplacé. Dans ce cas, les pilotes sont désenregistrés et ne reviennent jamais - seul un redémarrage de Tomcat peut vous sortir de ce bourbier. Cette solution évite cet enfer.

27voto

Spider Points 2205

Je vois souvent ce problème apparaître. Oui, Tomcat 7 le désenregistre automatiquement, mais est-ce vraiment prendre le 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 connexion à la base de données et vous débarrasser de toutes les alertes. C'est en tout cas ce que je fais.

Voici comment je procède.

Étape 1 : Enregistrer un écouteur

web.xml

    com.mysite.MySpecialListener

Étape 2 : Implémenter l'écouteur

com.mysite.MySpecialListener.java

public class MySpecialListener implements ServletContextListener {

    @Override
    public void contextInitialized(ServletContextEvent sce) {
        // Au démarrage de l'application, veuillez...

        // Habituellement, je vais créer un singleton ici, configurer mon pool, etc.
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        // Lors de l'arrêt de l'application, veuillez...

        // 1. Récupérer ce DataSource
        Context initContext = new InitialContext();
        Context envContext  = (Context)initContext.lookup("java:/comp/env");
        DataSource datasource = (DataSource)envContext.lookup("jdbc/database");

        // 2. Désenregistrer le pilote
        try {
            java.sql.Driver mySqlDriver = DriverManager.getDriver("jdbc:mysql://localhost:3306/");
            DriverManager.deregisterDriver(mySqlDriver);
        } catch (SQLException ex) {
            logger.info("Impossible de désenregistrer le pilote :".concat(ex.getMessage()));
        } 

        // 3. Pour plus de sécurité, supprimez la référence à dataSource pour que le GC puisse la nettoyer.
        dataSource = null;
    }

}

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

0 votes

Il est tellement agréable lorsque votre application se termine et que vos journaux indiquent simplement: [INFO]Arrêt de l'application. [INFO]Fermeture du pool de connexions. [INFO]Désenregistrement du pilote MySQL. [INFO]Fermeture du gestionnaire de fichiers journaux.

4 votes

Mais DataSource n'a PAS de méthode close

0 votes

Oui, il n'y a pas de méthode close() pour DataSource.

14voto

Il s'agit uniquement d'un problème d'inscription/désinscription du pilote dans mysql ou le classloader de l'application web de tomcat. Copiez le pilote mysql dans le dossier lib de tomcat (afin qu'il soit chargé directement par la JVM, et non par tomcat), et le message disparaîtra. Cela permet au pilote jdbc mysql d'être déchargé uniquement à l'arrêt de la JVM, et personne ne se soucie alors des fuites de mémoire.

2 votes

Cela ne fonctionne pas... J'ai essayé de copier le pilote jdbc comme vous l'avez dit : TOMCAT_HOME/lib/postgresql-9.0-801.jdbc4.jar - Tomcat 7.0\lib\postgresql-9.0-801.jdbc4.jar sans résultats...

6 votes

@Florito - vous devez également le supprimer de votre application Web WEB-INF/lib

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X