wake-up-neo.com

Um ein Speicherverlust zu verhindern, wurde der JDBC-Treiber zwangsweise nicht mehr registriert

Ich erhalte diese Meldung, wenn ich meine Webanwendung starte. Es läuft gut, aber ich bekomme diese Meldung beim Herunterfahren.

SEVERE: Eine Webanwendung hat den JBDC-Treiber [Oracle.jdbc.driver.OracleDriver] registriert, konnte die Registrierung jedoch nicht aufheben, wenn die Webanwendung gestoppt wurde. Um ein Speicherverlust zu verhindern, wurde der JDBC-Treiber zwangsweise nicht mehr registriert.

Jede Hilfe geschätzt.

296
mona

Seit Version 6.0.24 wird Tomcat mit der Funktion memory leak detection ausgeliefert. Dies kann zu Warnmeldungen führen, wenn ein JDBC 4.0-kompatibler Treiber im /WEB-INF/lib der webapp enthalten ist, der auto - registers selbst enthält beim Start von webapp mit der ServiceLoader-API , die sich beim Herunterfahren von webapp jedoch nicht automatisch - deregister selbst löste. Diese Nachricht ist rein informell. Tomcat hat die entsprechende Maßnahme zum Schutz vor Speicherlecks bereits ergriffen.

Was kannst du tun?

  1. Ignoriere diese Warnungen. Tomcat macht seinen Job richtig. Der tatsächliche Fehler befindet sich im Code einer anderen Person (dem betreffenden JDBC-Treiber), nicht in Ihrem. Seien Sie glücklich, dass Tomcat seine Arbeit ordnungsgemäß ausgeführt hat, und warten Sie, bis der JDBC-Treiberhersteller das Problem behoben hat, damit Sie den Treiber aktualisieren können. Andererseits sollten Sie keinen JDBC-Treiber im /WEB-INF/lib von webapp ablegen, sondern nur im /lib des Servers. Wenn Sie es immer noch im /WEB-INF/lib von webapp aufbewahren, sollten Sie es manuell registrieren und die Registrierung mit einer ServletContextListener abmelden.

  2. Führen Sie ein Downgrade auf Tomcat 6.0.23 oder älter durch, um diese Warnungen nicht zu stören. Aber es wird im stillen Gedächtnis bleiben. Ich bin mir nicht sicher, ob das gut zu wissen ist. Diese Art von Speicherverlusten sind eine der Hauptursachen für OutOfMemoryError-Probleme während der Hot-Deployments von Tomcat.

  3. Verschieben Sie den JDBC-Treiber in den /lib-Ordner von Tomcat, und verfügen Sie über eine Datenquelle für die Verbindungspools, um den Treiber zu verwalten. Beachten Sie, dass die integrierte Datenbank von Tomcat die Treiber beim Schließen nicht ordnungsgemäß abmeldet. Siehe auch Fehler DBCP-322 , der als WONTFIX geschlossen ist. Sie möchten DBCP lieber durch einen anderen Verbindungspool ersetzen, der seine Arbeit besser erledigt als DBCP. Zum Beispiel HikariCP , BoneCP oder vielleicht Tomcat JDBC Pool

281
BalusC

Entfernen Sie die Registrierung der Treiber in Ihrer kontextDestroyed () - Methode des Servlet-Kontextlisteners manuell:

        // 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

Obwohl Tomcat den JDBC-Treiber zwangsweise deregistriert, ist es dennoch empfehlenswert, alle Ressourcen zu löschen, die Ihre Webapp bei Kontextzerstörung erstellt hat, falls Sie zu einem anderen Servlet-Container wechseln, der die von Tomcat durchgeführten Speicherverletzungsprüfungen nicht durchführt.

Allerdings ist die Methodik der Blanko-Treiberabmeldung gefährlich. Einige von der DriverManager.getDrivers()-Methode zurückgegebene Treiber wurden möglicherweise vom übergeordneten ClassLoader (dh dem Classloader des Servlet-Containers) und nicht vom ClassLoader des Webapp-Kontexts geladen (z. B. befinden sie sich im lib-Ordner des Containers, nicht im Webapp-Ordner und werden daher gemeinsam genutzt der ganze Behälter). Das Aufheben der Registrierung wirkt sich auf alle anderen Webapps aus, die diese verwenden (oder sogar den Container selbst).

Daher sollte man überprüfen, dass der ClassLoader für jeden Treiber der ClassLoader der webapp ist, bevor die Registrierung aufgehoben wird. Also in der contextDestroyed () - Methode Ihres 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

Ich sehe, dass dieses Thema viel auftaucht. Ja, Tomcat 7 meldet es automatisch ab, aber es übernimmt wirklich die Kontrolle über Ihren Code und eine gute Kodierungspraxis? Sicher möchten SIE wissen, dass Sie über den richtigen Code verfügen, um alle Objekte zu schließen, Datenbankverbindungspool-Threads zu schließen und alle Warnungen zu beseitigen. Ich mache das sicherlich.

So mache ich es.

Schritt 1: Einen Listener registrieren

web.xml

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

Schritt 2: Implementiere den Listener

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

}

Fühlen Sie sich frei zu kommentieren und/oder hinzuzufügen ...

25
Spider

Dies ist ein reines Treiberregistrierungs-/Deregistrierungsproblem in mysqls Treiber oder tomcats webapp-classloader. Kopieren Sie den mysql-Treiber in den lib-Ordner von tomcats (so dass er direkt von jvm und nicht von Tomcat geladen wird), und die Nachricht wird gelöscht. Dies bedeutet, dass der mysql jdbc-Treiber nur beim Herunterfahren der JVM entladen wird, und dann kümmert sich niemand um Speicherlecks.

14

Wenn Sie diese Nachricht aus einem von Maven erstellten Krieg erhalten, ändern Sie den Gültigkeitsbereich des JDBC-Treibers in "zur Verfügung gestellt" und legen Sie eine Kopie davon im lib-Verzeichnis ab. So was: 

<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

Lösung für App-Bereitstellungen

Dies ist ein Listener, den ich geschrieben habe, um das Problem zu lösen: Er erkennt automatisch, ob der Treiber sich selbst registriert hat, und reagiert entsprechend 

Wichtig: Es ist dafür gedacht, NUR wenn das Treiber-Jar in WEB-INF/lib bereitgestellt wird, nicht in der Tomcat/lib verwendet zu werden, wie viele meinen, so dass jede Anwendung sich um ihren eigenen Treiber kümmern kann und laufen auf einem unberührten Kater. So sollte es IMHO sein.

Konfigurieren Sie einfach den Listener in Ihrer web.xml vor allen anderen und genießen Sie es.

fügen Sie am oberen Rand von web.xml hinzu:

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

speichern Sie als 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

Ich werde dazu etwas hinzufügen, das ich in den Frühjahrsforen gefunden habe. Wenn Sie Ihr JDBC-Treiber-Jar in den Tomcat-Lib-Ordner verschieben, anstatt es mit Ihrer Web-App bereitzustellen, scheint die Warnung zu verschwinden. Ich kann bestätigen, dass dies für mich funktioniert hat

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

6
Collin Peters

Ich fand, dass die Implementierung einer einfachen destroy () -Methode zum Abmelden von JDBC-Treibern gut funktioniert.

/**
 * 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

Ich hatte ein ähnliches Problem, aber zusätzlich bekam ich einen Java-Heap-Space-Fehler, wenn ich JSP-Seiten mit laufendem Tomcat-Server modifizierte/speicherte. Daher wurde der Kontext nicht vollständig aufgeladen. 

Meine Versionen waren Apache Tomcat 6.0.29 und JDK 6u12.

Ein Upgrade des JDK auf 6u21 wie in References im Abschnitt URL http://wiki.Apache.org/Tomcat/MemoryLeakProtection vorgeschlagen, löste das Java-Heap-Speicherproblem (Kontext lädt jetzt OK), obwohl der JDBC-Treiberfehler weiterhin angezeigt wird.

2

Um dieses Speicherverlust zu verhindern, wird der Treiber beim Herunterfahren des Kontextes einfach abgemeldet.

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>

Quelle die mich zu diesem Bugfix inspiriert hat.

1
King-Wizard

Dieses Problem ist bei der Bereitstellung meiner Grails-Anwendung unter AWS aufgetreten. Dies ist eine Angelegenheit des JDBC-Standardtreibers org.h2 driver. Wie Sie dies in Datasource.groovy in Ihrem Konfigurationsordner sehen können. Wie du unten sehen kannst: 

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

Kommentieren Sie diese Zeilen dort, wo org.h2.Driver in der Datei datasource.groovy angegeben ist, wenn Sie diese Datenbank nicht verwenden .. __ Anderenfalls müssen Sie diese Datenbank-JAR-Datei herunterladen.

Vielen Dank .

0
PyDevSRS

Ich habe das gleiche Problem mit Tomcat Version 6.026 gefunden.

Ich habe die Mysql JDBC.jar in der WebAPP Library sowie in Tomcat Lib verwendet.

Um dies zu beheben, entfernen Sie das Jar aus dem Tomcat-Ordner "lib".

Ich verstehe also, dass Tomcat den JDBC-Speicherverlust richtig verarbeitet. Wenn die MYSQL-Jdbc-Dose in WebApp und Tomcat Lib dupliziert wird, kann Tomcat nur die im Tomcat-Lib-Ordner vorhandene Dose verarbeiten.

0
Bharat