Para evitar una pérdida de memoria, el controlador JDBC ha sido desregistrado por la fuerza

Recibo este mensaje cuando ejecuto mi aplicación web. Funciona bien pero recibo este mensaje durante el cierre.

SEVERE: una aplicación web registró el controlador JBDC [oracle.jdbc.driver.OracleDriver] pero no pudo anular el registro cuando se detuvo la aplicación web. Para evitar una pérdida de memoria, el controlador JDBC ha sido desregistrado a la fuerza.

Cualquier ayuda apreciada.

Desde la versión 6.0.24, Tomcat se envía con una función de detección de fuga de memoria , que a su vez puede generar este tipo de mensajes de advertencia cuando hay un controlador compatible con JDBC 4.0 en la webapp /WEB-INF/lib que se autoregistra durante el inicio de la aplicación utilizando la API de ServiceLoader , pero que no se ServiceLoader automáticamente durante el cierre de webapp. Este mensaje es puramente informal, Tomcat ya ha tomado la acción de prevención de fuga de memoria en consecuencia.

¿Qué puedes hacer?

  1. Ignora esas advertencias. Tomcat está haciendo su trabajo bien. El error real está en el código de otra persona (el controlador JDBC en cuestión), no en el tuyo. Sea feliz de que Tomcat hizo su trabajo correctamente y espere hasta que el proveedor del controlador JDBC lo solucione para que pueda actualizar el controlador. Por otro lado, no se supone que deba colocar un controlador JDBC en webapp /WEB-INF/lib , sino solo en /lib del servidor. Si aún lo mantiene en webapp /WEB-INF/lib , entonces debe registrarlo manualmente y eliminarlo mediante un ServletContextListener .

  2. Baja a Tomcat 6.0.23 o anterior para que no te molesten esas advertencias. Pero silenciosamente seguirá escapándose de la memoria. No estoy seguro si eso es bueno saberlo después de todo. OutOfMemoryError de memoria es una de las principales causas detrás de los problemas de OutOfMemoryError durante los despliegues en caliente de Tomcat.

  3. Mueva el controlador JDBC a la carpeta /lib Tomcat y tenga un origen de datos agrupado de conexión para administrar el controlador. Tenga en cuenta que el DBCP integrado de Tomcat no cancela el registro de los controladores de forma adecuada al cerrar. Vea también el error DBCP-322 que está cerrado como WONTFIX. Preferiría reemplazar el DBCP por otro grupo de conexiones que esté haciendo su trabajo mejor que DBCP. Por ejemplo, HikariCP , BoneCP o quizás Tomcat JDBC Pool .

En su método de escucha de contexto de servlet contextDestroyed (), anule el registro manual de los controladores:

  // 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, String.format("deregistering jdbc driver: %s", driver)); } catch (SQLException e) { LOG.log(Level.SEVERE, String.format("Error deregistering driver %s", driver), e); } } 

Aunque Tomcat desregistra forzosamente el controlador JDBC, es una buena práctica limpiar todos los recursos creados por su aplicación web en la destrucción de contexto en caso de que se mueva a otro contenedor de servlets que no hace las comprobaciones de prevención de fuga de memoria que hace Tomcat.

Sin embargo, la metodología de cancelación de registro de manta es peligrosa. Algunos controladores devueltos por el método DriverManager.getDrivers() pueden haber sido cargados por el ClassLoader primario (es decir, el cargador de clases del contenedor de servlets) y no por el ClassLoader del contexto de la aplicación web (por ejemplo, pueden estar en la carpeta lib del contenedor, no en la aplicación web; compartido a través de todo el contenedor). Eliminar el registro de estos afectará a cualquier otra aplicación de Internet que pueda estar usándola (o incluso el contenedor en sí).

Por lo tanto, uno debe verificar que el ClassLoader para cada controlador sea el ClassLoader de la aplicación antes de anular su registro. Entonces, en el método 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'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); } } } 

Veo que este problema surge mucho. Sí, Tomcat 7 lo cancela automáticamente, pero ¿REALMENTE toma el control de tu código y una buena práctica de encoding? Seguramente USTED quiere saber que tiene todo el código correcto para cerrar todos sus objetos, cerrar los hilos del grupo de conexión de la base de datos y deshacerse de todas las advertencias. Ciertamente lo hago

Así es como lo hago.

Paso 1: registrar un oyente

web.xml

  com.mysite.MySpecialListener  

Paso 2: Implementa el oyente

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

Por favor, siéntase libre de comentar y / o agregar …

Esto es simplemente un problema de registro / cancelación del controlador en el controlador de mysql o en el cargador de clase de webapp-tomcats. Copie el controlador de mysql en la carpeta lib de tomcats (para que lo cargue jvm directamente, no por tomcat), y el mensaje desaparecerá. Eso hace que el controlador mysql jdbc se descargue solo en el cierre de JVM, y a nadie le importan las pérdidas de memoria.

Si recibe este mensaje de una guerra construida por Maven, cambie el scope del controlador JDBC a proporcionado y ponga una copia en el directorio lib. Me gusta esto:

  mysql mysql-connector-java 5.1.18  provided  

Solución para implementaciones por aplicación

Este es un oyente que escribí para resolver el problema: autodetecta si el conductor se ha registrado y actúa en consecuencia.

Importante: debe usarse ÚNICAMENTE cuando el contenedor de controladores se implementa en WEB-INF / lib , no en Tomcat / lib, como sugieren muchos, para que cada aplicación pueda ocuparse de su propio controlador y ejecutar un Tomcat intacto. . Esa es la forma en que debería ser en mi humilde opinión.

Simplemente configure el oyente en su web.xml antes que cualquier otro y disfrute.

agregar cerca de la parte superior de web.xml :

  utils.db.OjdbcDriverRegistrationListener  

guardar como 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(String.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(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"); } } } 

Agregaré a esto algo que encontré en los foros de Spring. Si mueve su jar de controlador JDBC a la carpeta lib de tomcat, en lugar de desplegarlo con su aplicación web, la advertencia parece desaparecer. Puedo confirmar que esto funcionó para mí

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

Descubrí que implementar un método simple destroy () para anular el registro de cualquier controlador JDBC funciona muy bien.

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

Estaba teniendo un problema similar, pero además estaba obteniendo un error de Java Heap Space cada vez que modificaba / guardaba páginas JSP con el servidor Tomcat en ejecución, por lo tanto, el contexto no se recargó por completo.

Mis versiones fueron Apache Tomcat 6.0.29 y JDK 6u12.

La actualización de JDK a 6u21 como se sugiere en la sección de Referencias de la URL http://wiki.apache.org/tomcat/MemoryLeakProtection resolvió el problema de Java Heap Space (el contexto ahora vuelve a cargarse correctamente ) aunque el error del controlador JDBC aún aparece.

Encontré el mismo problema con la versión 6.026 de Tomcat.

Usé el Mysql JDBC.jar en la Biblioteca WebAPP, así como en TOMCAT Lib.

Para solucionar lo anterior, quite el Jar de la carpeta lib de TOMCAT.

Entonces, lo que entiendo es que TOMCAT está manejando la fuga de memoria JDBC correctamente. Pero si el contenedor MYSQL Jdbc está duplicado en WebApp y Tomcat Lib, Tomcat solo podrá gestionar el contenedor presente en la carpeta Tomcat Lib.

Me enfrenté a este problema cuando estaba implementando mi aplicación Grails en AWS. Esta es la cuestión del controlador predeterminado de org.h2 de JDBC. Como puede ver esto en Datasource.groovy dentro de su carpeta de configuración. Como puede ver abajo :

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

Comenta esas líneas donde se menciona org.h2.Driver en el archivo datasource.groovy, si no estás usando esa base de datos. De lo contrario, debe descargar ese archivo jar de la base de datos.

Gracias .

Para evitar esta pérdida de memoria, simplemente anule el registro del controlador al apagar el contexto.

pom.xml

   4.0.0 com.mywebsite emusicstore 1.0-SNAPSHOT    org.apache.maven.plugins maven-compiler-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

    com.emusicstore.utils.MyWebAppContextListener    

Fuente que me inspiró para esta corrección de errores.

Eliminar la aplicación (tomcat6) lo resuelve. Los archivos conf se conservan. Se rompe de alguna manera. No estoy muy seguro de cómo lo hace.