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

Peut être en double 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 lors du démarrage de l'application Web en utilisant l'API ServiceLoader, mais qui ne s'est pas auto-désenregistré lors de l'arrêt de l'application Web. Ce message est purement informatif, Tomcat a déjà pris les mesures nécessaires pour prévenir les fuites de mémoire.

Que pouvez-vous faire?

  1. Ignorez ces avertissements. Tomcat fait correctement son travail. Le véritable bug se trouve dans le code de quelqu'un d'autre (le pilote JDBC en question), pas dans le vôtre. Soyez heureux que Tomcat ait fait correctement son travail et attendez que le vendeur du pilote JDBC le corrige pour que vous puissiez mettre à jour le pilote. D'autre part, vous ne devez 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, vous devriez l'enregistrer et le désenregistrer manuellement en utilisant un ServletContextListener.

  2. Revenez à Tomcat 6.0.23 ou antérieur pour ne plus être dérangé par ces avertissements. Mais la mémoire continuera de fuir silencieusement. Pas sûr que ce soit une bonne nouvelle après tout. Ce type de fuites de mémoire est l'une des principales causes des problèmes d'erreurs de mémoire insuffisante lors des hotdeployments de Tomcat.

  3. Déplacez le pilote JDBC dans le dossier /lib de Tomcat et utilisez une source de données avec mise en pool de connexions pour gérer le pilote. Notez que le DBCP intégré à Tomcat ne désenregistre pas correctement les pilotes à la fermeture. Veuillez également consulter le bogue DBCP-322 qui est clos comme WONTFIX. Vous préférerez probablement remplacer le 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 d'une fuite de mémoire, c'est un avertissement que Tomcat a pris une certaine action forcée pour prévenir une fuite

0 votes

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

0 votes

Pouvons-nous le laisser ainsi ou suggérez-vous d'utiliser la correction indiqué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ésenregistrez manuellement les pilotes :

// Cela désenregistre 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ésenregistrement du pilote jdbc : %s", driver));
    } catch (SQLException e) {
        LOG.log(Level.SEVERE, String.format("Erreur lors de la désenregistrement du pilote %s", driver), e);
    }
}

3 votes

Cela fonctionne! javabeat.net/servletcontextlistener-example pourrait vous aider à implémenter un auditeur de contexte de servlet

22 votes

Ceci est potentiellement dangereux dans un environnement partagé, car vous pourriez ne pas vouloir désenregistrer tous les pilotes JDBC disponibles. Voir ma réponse pour une approche plus sûre.

96voto

daiscog Points 3088

Bien que Tomcat désenregistre automatiquement le pilote JDBC pour vous, il est quand même judicieux 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 fait pas les vérifications de prévention des fuites de mémoire que fait Tomcat.

Cependant, la méthodologie de désenregistrement en bloc du pilote est dangereuse. Certains pilotes retournés par la méthode DriverManager.getDrivers() peuvent avoir été chargés par le Chargeur de classes parent (c'est-à-dire le chargeur de classes du conteneur de servlet) et non pas le Chargeur de classes du contexte de l'application Web (par exemple, ils peuvent se trouver dans le dossier lib du conteneur, et non pas de l'application Web, et sont donc partagés dans tout le conteneur). Désenregistrer ces pilotes affectera toutes les autres applications Web qui pourraient les utiliser (ou même le conteneur lui-même).

Par conséquent, il est recommandé de vérifier que le Chargeur de classes de chaque pilote est bien celui de l'application Web avant de le désenregistrer. Ainsi, dans la méthode contextDestroyed() de votre ContextListener:

public final void contextDestroyed(ServletContextEvent sce) {
    // ... D'abord fermer toutes tâches en arrière-plan qui pourraient utiliser la base de données ...
    // ... Puis fermer toutes les pools de connexions à la base de données ...

    // Maintenant désenregistrer les pilotes JDBC dans le Chargeur de classes de ce contexte :
    // Obtenir le Chargeur de classes de l'application Web
    ClassLoader cl = Thread.currentThread().getContextClassLoader();
    // Boucle sur 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 Chargeur de classes de l'application Web, donc le désenregistrer :
            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 Chargeur de classes de l'application Web et peut être utilisé ailleurs
            log.trace("Ne pas désenregistrer le pilote JDBC {} car il n'appartient pas au Chargeur de classes 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 ClassLoader, et non pas 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 pétrin. 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, arrêter les threads de pool de connexion à la base de données et vous débarrasser de tous les avertissements. C'est certainement mon cas.

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, s'il vous plaît...

        // En général, je crée un singleton ici, mets en place mon pool, etc.
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        // À l'arrêt de l'application, s'il vous plaît...

        // 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ûreté, supprimez la référence à dataSource pour que GC puisse en bénéficier.
        dataSource = null;
    }

}

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

0 votes

Cela semble tellement charmant 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ésinscription de MySQLDriver. <[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 simplement 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, pas 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