为什么我收到JDBC驱动警告和ThreadLocal错误?

3

我正在GlassFish上运行我的应用程序,使用了Spring Security和Hibernate。 当我运行应用程序时,GlassFish控制台会显示以下警告和错误。如何避免它们?

WARNING:   The web application [] registered the JDBC driver [com.mysql.jdbc.Driver] but failed to unregister it when the web application was stopped. To prevent a memory leak, the JDBC Driver has been forcibly unregistered.
SEVERE:   The web application [] created a ThreadLocal with key of type [java.lang.ThreadLocal] (value [java.lang.ThreadLocal@1087985b]) and a value of type [org.hibernate.internal.SessionImpl] (value [SessionImpl(PersistenceContext[entityKeys=[],collectionKeys=[]];ActionQueue[insertions=[] updates=[] deletions=[] collectionCreations=[] collectionRemovals=[] collectionUpdates=[] unresolvedInsertDependencies=UnresolvedEntityInsertActions[]])]) but failed to remove it when the web application was stopped. Threads are going to be renewed over time to try and avoid a probable memory leak.
SEVERE:   The web application [] created a ThreadLocal with key of type [net.sf.json.AbstractJSON$1] (value [net.sf.json.AbstractJSON$1@362386d7]) and a value of type [java.util.HashSet] (value [[]]) but failed to remove it when the web application was stopped. Threads are going to be renewed over time to try and avoid a probable memory leak.
SEVERE:   The web application [] created a ThreadLocal with key of type [net.sf.json.AbstractJSON$1] (value [net.sf.json.AbstractJSON$1@362386d7]) and a value of type [java.util.HashSet] (value [[]]) but failed to remove it when the web application was stopped. Threads are going to be renewed over time to try and avoid a probable memory leak.

hibernate.cfg.xml

<hibernate-configuration>

    <session-factory>

        <!-- Database connection settings -->
        <property name="connection.driver_class">
            com.mysql.jdbc.Driver
        </property>
        <property name="connection.url">
            jdbc:mysql://localhost:3306/myproject
        </property>
        <property name="connection.username">root</property>
        <property name="connection.password"></property>

        <!-- JDBC connection pool (use the built-in) -->
        <property name="connection.pool_size">12</property>

        <!-- SQL dialect -->
        <property name="dialect">
            org.hibernate.dialect.MySQLDialect
        </property>



        <!-- Enable Hibernate's automatic session context management -->
        <property name="current_session_context_class">thread</property>

<!--         Disable the second-level cache -->

<!-- <property name="cache.provider_class">
            org.hibernate.cache.EhCacheProvider
        </property>

        <property name="hibernate.cache.use_query_cache">true</property>-->


        <!-- Echo all executed SQL to stdout -->
        <property name="show_sql">true</property>

HibernateUtil.java

public class HibernateUtil {

   private static ServiceRegistry serviceRegistry;
   private static final ThreadLocal<Session> threadLocal = new ThreadLocal();
   private static SessionFactory sessionFactory;

   private static SessionFactory configureSessionFactory() {
        try {
            Configuration configuration = new Configuration();
            configuration.configure();
            serviceRegistry = new ServiceRegistryBuilder().applySettings(configuration.getProperties()).buildServiceRegistry();

            sessionFactory = configuration.buildSessionFactory(serviceRegistry);

            return sessionFactory;
        } catch (HibernateException e) {
            System.out.append("** Exception in SessionFactory **");
            e.printStackTrace();
        }
       return sessionFactory;
  }     

  static {
    try {
      sessionFactory = configureSessionFactory();
    } catch (Exception e) {
      System.err.println("%%%% Error Creating SessionFactory %%%%");
      e.printStackTrace();
    }
  }

  private HibernateUtil() {
  }

  public static SessionFactory getSessionFactory() {
    return sessionFactory;
  }

  public static Session getSession() throws HibernateException {
    Session session = threadLocal.get();

    if (session == null || !session.isOpen()) {
      if (sessionFactory == null) {
        rebuildSessionFactory();
      }
      session = (sessionFactory != null) ? sessionFactory.openSession() : null;
      threadLocal.set(session);
    }

    return session;
  }

  public static void rebuildSessionFactory() {
    try {
      sessionFactory = configureSessionFactory();
    } catch (Exception e) {
      System.err.println("%%%% Error Creating SessionFactory %%%%");
      e.printStackTrace();
    }
  }

  public static void closeSession() throws HibernateException {
    Session session = (Session) threadLocal.get();
    threadLocal.set(null);

    if (session != null) {
      session.close();
    }
  }
}

1
我从个人经验中知道,关于JDBCDriver的问题不值得担心。基本上这意味着在程序结束之前你没有关闭数据库连接。 - JamesENL
只要您已经配置了Spring以注销驱动程序,请咨询Spring团队为什么会发生这种情况。 - Roman C
请查看此问题 - Tiny
3个回答

8
这些是应用程序重新部署时服务器保持运行的情况下可能发生的错误消息。
如果是关闭场景或开发重新部署,这些消息可以安全地忽略,只有在需要在生产环境中进行重新部署时才变得重要,而这种情况很少发生。大多数情况下,即使在生产中,我们也希望停止服务器进程并完全重新启动它。以下是每个消息含义的一些详细信息:
消息1-应用程序停止时未注销驱动程序:
警告:Web应用程序[]注册了JDBC驱动程序[com.mysql.jdbc.Driver],但在Web应用程序停止时未能注销它。为防止内存泄漏,强制注销了JDBC驱动程序。
JDBC驱动程序在JVM级别的单例中在启动时注册,这是由服务器通过在服务器级别的文件夹中发布驱动程序jar来完成的。
在这种情况下,应用似乎携带了驱动程序本身,这不是部署驱动程序的方式。

为解决此问题,请从应用程序中删除驱动程序,并在服务器级别注册它。如果多个应用程序具有相同的驱动程序,则还会导致内存泄漏-请参见此answer

消息2- ThreadLocal未清除:

SEVERE:Web应用程序[]创建了一个ThreadLocal,其键类型为[java.lang.ThreadLocal](值[java.lang.ThreadLocal@1087985b]),值类型为[org.hibernate.internal.SessionImpl],但在停止Web应用程序时未能将其删除。随着时间的推移,线程将被更新以尝试避免可能的内存泄漏。

这意味着一个应用程序Spring线程在线程中存储了一个Hibernate会话(每个线程都有一个数据存储区,可以通过ThreadLocal附加东西)。

但是,在重新启动应用程序时,线程没有清理会话,因此存储在线程中的此变量可以在重新使用线程时在重新部署后可见。

这可能令人惊讶,但最糟糕的情况是会指向其他对象,这些对象本身指向类,而这些类在重新部署之前指向旧的类加载器。
这意味着由于与先前部署的对象的泄漏“链接”,对象树的大部分将无法进行垃圾回收。结果是ClassLoader Memory Leak
该消息表示,由于未清理的ThreadLocals,可能会发生此情况,并且将采取一些预防措施(开始杀死线程并创建新线程,而不是池化,以摆脱泄漏的线程本地变量)。
总之,如果您不需要在生产中重新部署并始终重新启动服务器,则可以安全地忽略这些消息。

非常感谢您的全面回答,我还有一个问题,您建议使用哪个软件进行测试?Java Web应用程序? - AlexCartio1
在我的情况下,我需要在生产环境中重新部署。有没有一种方法可以手动清理这些线程本地变量? - mpmp

3
为了消除JDBC驱动程序警告,需要在应用程序关闭时运行以下命令:
String url = "your JDBC url";
Driver driver = DriverManager.getDriver(url);
DriverManager.deregisterDriver(driver);

如果您正在使用Spring bean,您可以将其放置在DisposableBeandestroy()方法中。
在servlet环境中,您可以使用ServletContextListener
public class CleanupListener implements ServletContextListener {

    public void contextDestroyed(ServletContextEvent arg0) {
        // enter cleanup code here
    }

    public void contextInitialized(ServletContextEvent arg0) { }
}

在web.xml中进行设置:

<listener>
    <listener-class>com.example.CleanupListener</listener-class>
</listener>

谢谢,你说的是在应用程序关闭时运行这个吗?第二个问题怎么样? - AlexCartio1
@AlexCartio1 你可以在Web应用程序中注册一个监听器,当应用程序关闭时会被调用,你可以在其中放置清理代码。对于Spring应用程序,你可以使用DisposableBean。如果不是Spring但在Servlet环境中,你可以使用ServletContextListener - holmis83
你能给我一个这样的监听器的例子吗? - AlexCartio1
@AlexCartio1 扩展了我的答案。 - holmis83

1
从JDK6开始,如果在类路径中找到任何Driver类,则JDBC驱动程序将自动加载,而无需使用Class.forName()方法,这可能会导致出现此类错误消息。最好为您的应用程序编写一个监听器,并在应用程序关闭时注销每个驱动程序。您可以使用DiverManager#getDrivers()方法获取所有已注册的驱动程序,并逐个取消注册。

你能给我一个这样的监听器的例子吗? - AlexCartio1
1
请在ServletContextListener中进行测试,并在上下文销毁时注销您的驱动程序。您可以在http://www.mkyong.com/servlet/what-is-listener-servletcontextlistener-example/找到ServletContextListener的示例示例。 - Naveen Ramawat

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接