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. Cela 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 n’a pas été enregistré.

Toute aide appréciée.

Depuis la version 6.0.24, Tomcat est livré avec une fonction de détection des memory leaks , qui à son tour peut générer ce type de messages d’avertissement lorsqu’il existe un pilote compatible JDBC 4.0 dans l’application /WEB-INF/lib qui s’enregistre automatiquement lors du démarrage de webapp à l’aide de l’ API ServiceLoader , mais qui ne s’est pas auto- annulé pendant l’arrêt de l’application Web. Ce message est purement informel, Tomcat a déjà pris l’action de prévention des memory leaks en conséquence.

Que pouvez-vous faire?

  1. Ignorez ces avertissements. Tomcat fait bien son travail. Le bogue 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 fasse réparer pour pouvoir mettre à jour le pilote. D’un autre côté, vous n’êtes pas censé déposer un pilote JDBC dans /WEB-INF/lib webapp, mais uniquement dans /lib du serveur. Si vous le conservez toujours dans /WEB-INF/lib webapp, vous devez l’enregistrer et le désenregistrer manuellement à l’aide d’un ServletContextListener .

  2. Rétrogradez à Tomcat 6.0.23 ou plus ancien pour ne pas être dérangé par ces avertissements. Mais il y aura des memory leaks en silence. Je ne sais pas si c’est bon à savoir après tout. Ce type de fuite de mémoire est l’une des causes OutOfMemoryError problèmes d’ OutOfMemoryError lors des déploiements à chaud de Tomcat.

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

Dans votre méthode contextDestroyed () de programme d’écoute de contexte de servlet, désenregistrez manuellement les pilotes:

  // This manually deregisters JDBC driver, which prevents Tomcat 7 from complaining about memory leaks wrto this class Enumeration drivers = DriverManager.getDrivers(); while (drivers.hasMoreElements()) { Driver driver = drivers.nextElement(); try { DriverManager.deregisterDriver(driver); LOG.log(Level.INFO, Ssortingng.format("deregistering jdbc driver: %s", driver)); } catch (SQLException e) { LOG.log(Level.SEVERE, Ssortingng.format("Error deregistering driver %s", driver), e); } } 

Bien que Tomcat désenregistre forcément le pilote JDBC pour vous, il convient néanmoins de nettoyer toutes les ressources créées par votre application Web lors de la destruction du contexte si vous déplacez un autre conteneur de servlet qui ne vérifie pas les memory leaks.

Cependant, la méthodologie de désinscription des conducteurs généraux 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 classloader du conteneur de servlet) et non par le classLoader du contexte Webapp (par exemple, ils peuvent se trouver dans le dossier lib du conteneur et non partagé sur l’ensemble du conteneur). Leur annulation affectera toutes les applications Web qui peuvent 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 radier. 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 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); } } } 

Je vois ce problème se poser beaucoup. Oui, Tomcat 7 le désenregistre automatiquement, mais cela prend VRAIMENT le contrôle de votre code et une bonne pratique de codage? Vous devez sûrement savoir que vous avez tout le code correct en place pour fermer tous vos objects, fermer les threads du pool de connexion aux bases de données et supprimer tous les avertissements. Je fais certainement.

C’est comme ça que je le fais.

Étape 1: Enregistrer un auditeur

web.xml

  com.mysite.MySpecialListener  

Étape 2: implémenter le programme d’écoute

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

S’il vous plaît n’hésitez pas à commenter et / ou append …

Ceci est purement une question d’enregistrement / de 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é par jvm directement, pas par tomcat), et le message disparaîtra. Cela fait que le pilote mysql jdbc est déchargé uniquement à la fermeture de la JVM, et personne ne se soucie alors des memory leaks.

Si vous obtenez ce message à partir d’une guerre créée par Maven, changez la scope du pilote JDBC pour qu’il soit fourni, et mettez-en une copie dans le répertoire lib. Comme ça:

  mysql mysql-connector-java 5.1.18  provided  

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

Important: il est destiné à être utilisé UNIQUEMENT lorsque le pilote jar est déployé dans WEB-INF / lib , pas dans Tomcat / lib, comme beaucoup le suggèrent, afin que chaque application puisse prendre en charge son propre pilote et s’exécuter sur un Tomcat intact. . C’est comme ça que ça devrait être IMHO.

Il suffit de configurer le listener dans votre web.xml avant tout autre et en profiter.

ajoutez près du haut de web.xml :

  utils.db.OjdbcDriverRegistrationListener  

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 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(Ssortingng.format("registered jdbc driver: %sv%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(Ssortingng.format("deregistering jdbc driver: %s", driver)); } catch (SQLException e) { LOG.warn( Ssortingng.format("Error deregistering driver %s", driver), e); } this.driver = null; } else { LOG.warn("No driver to deregister"); } } } 

Je vais append à cela quelque chose que j’ai trouvé sur les forums de spring. Si vous déplacez votre 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

J’ai trouvé que l’implémentation d’une méthode destroy () simple pour désenregistrer les pilotes JDBC fonctionne parfaitement.

 /** * Destroys the servlet cleanly by unloading JDBC drivers. * * @see javax.servlet.GenericServlet#destroy() */ public void destroy() { Ssortingng prefix = getClass().getSimpleName() +" destroy() "; ServletContext ctx = getServletContext(); try { Enumeration 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"); } 

J’avais un problème similaire, mais je recevais en plus une erreur Java Heap Space chaque fois que je modifiais / sauvegardais des pages JSP avec le serveur Tomcat en cours d’exécution, donc 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 de Java Heap Space (le contexte se recharge maintenant correctement), même si une erreur du pilote JDBC apparaît toujours.

J’ai trouvé le même problème avec la version 6.026 de Tomcat.

J’ai utilisé le JDBC.jar Mysql dans la bibliothèque WebAPP ainsi que dans TOMCAT Lib.

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

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

J’ai rencontré ce problème lorsque je déployais mon application Grails sur AWS. C’est une question de pilote JDBC par défaut du pilote org.h2 . Comme vous pouvez le voir dans le 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 là où il est mentionné org.h2.Driver dans le fichier datasource.groovy , si vous n’utilisez pas cette firebase database. Sinon, vous devez télécharger ce fichier jar de firebase database.

Merci .

Pour éviter cette fuite de mémoire, enregistrez simplement le pilote lors de l’arrêt du contexte.

pom.xml

 < ?xml version="1.0" encoding="UTF-8"?>  4.0.0 com.mywebsite emusicstore 1.0-SNAPSHOT    org.apache.maven.plugins maven-comstackr-plugin 3.7.0  1.9 1.9        org.hibernate hibernate-core 4.0.1.Final   org.hibernate.javax.persistence hibernate-jpa-2.0-api 1.0.1.Final    mysql mysql-connector-java 8.0.11    javax.servlet servlet-api 2.5 provided    

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 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"?>   com.emusicstore.utils.MyWebAppContextListener    

Source qui m’a inspiré pour ce correctif.

La suppression de l’application (tomcat6) le résout. Les fichiers conf sont conservés. Il se brise en quelque sorte. Je ne suis pas vraiment sûr de la façon dont cela se passe.