为啥我会收到 JDBC 驱动程序警告和 ThreadLocal 错误?

Posted

技术标签:

【中文标题】为啥我会收到 JDBC 驱动程序警告和 ThreadLocal 错误?【英文标题】:Why am I receiving JDBC driver warning and ThreadLocal errors?为什么我会收到 JDBC 驱动程序警告和 ThreadLocal 错误? 【发布时间】:2014-03-28 17:30:37 【问题描述】:

我在 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();
    
  

【问题讨论】:

我从个人经验中知道,关于 JDBCDriver 的问题不值得担心。基本上这意味着您在程序结束之前没有关闭数据库连接。 当您配置 Spring 以注销驱动程序时,请咨询 Spring 团队为什么不发生这种情况。 见this问题。 【参考方案1】:

要消除 JDBC 驱动程序警告,请在应用程序关闭时运行以下命令:

String url = "your JDBC url";
Driver driver = DriverManager.getDriver(url);
DriverManager.deregisterDriver(driver);

如果您使用的是 Spring bean,则可以将其放在 DisposableBean 的 destroy() 方法中。

在 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 您可以在应用程序关闭时调用的 webapp 中注册一个侦听器,您可以在其中放置清理代码。对于 Spring 应用程序,您可以使用 DisposableBean。如果不是 Spring 而在 servlet 环境中,可以使用ServletContextListener 你能给我一个这样的听众的例子吗? @AlexCartio1 扩展了我的答案。【参考方案2】:

这些是在服务器保持运行时重新部署应用程序时可能发生的错误消息。

如果是关闭场景或开发重新部署,这些消息可以安全地忽略,只有在您需要在生产中重新部署时它们才变得重要,这很少见。大多数时候,即使在生产中,我们也希望停止服务器进程并完全重新启动它。这是每个消息含义的一些细节:

消息 1 - 应用程序停止时未取消注册驱动程序:

警告:Web 应用程序 [] 注册了 JDBC 驱动程序 [com.mysql.jdbc.Driver]但是web时注销失败 应用程序已停止。为了防止内存泄漏,JDBC 驱动程序具有 被强制注销。

JDBC 驱动程序在启动时在 JVM 级别的单例中注册,这意味着由服务器通过在服务器级别的文件夹中发布驱动程序 jar 来完成。

在这种情况下,应用程序本身似乎带有驱动程序,这不是驱动程序的部署方式。

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

消息 2 - 未清理 ThreadLocal:

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

这意味着一个应用程序 spring 线程在线程中存储了一个 Hibernate 会话(每个线程作为数据存储,可以通过ThreadLocal 附加内容)。

但是当应用程序重新启动时线程并没有清理会话,所以这个存储在线程中的变量在重新部署后线程被重用时是可见的。

这可能令人惊讶,但最糟糕的是会话指向其他对象,这些对象本身指向类,而这些对象又指向重新部署之前的旧类加载器。

这意味着对象树的很大一部分将不会被垃圾收集,因为此泄漏的“链接”指向先前部署的对象。结果是ClassLoader Memory Leak。

消息说这种情况可能是由于未清理的 ThreadLocals 而发生的,并且将采取一些预防措施(开始杀死线程并创建新线程而不是池化,以消除泄漏的线程局部变量)。

总而言之,如果您不需要在生产中重新部署并始终重新启动服务器,则可以安全地忽略这些消息。

【讨论】:

非常感谢您的全面回答,我还有一个问题,您建议使用哪种软件进行测试? Java 网络应用程序? 就我而言,我需要在生产中重新部署。有没有办法可以手动清理这些线程局部变量?【参考方案3】:

JDK6 以后的 JDBC 驱动程序如果在 classpath 中找到任何没有 Class.forName() 方法的 Driver 类,则会自动加载,并且可能会导致此类错误消息。最好为您的应用程序编写一个侦听器,并在应用程序关闭时取消注册每个驱动程序。您可以使用 DiverManager#getDrivers() 方法获取所有已注册的驱动程序,并可以一一注销。

【讨论】:

你能给我一个这样的听众的例子吗? 请在上下文销毁时使用 ServletContextListener 进行测试并注销您的驱动程序。您可以在mkyong.com/servlet/… 找到 ServletContextListener 的示例示例 ...

以上是关于为啥我会收到 JDBC 驱动程序警告和 ThreadLocal 错误?的主要内容,如果未能解决你的问题,请参考以下文章

为啥我会在这里收到类型安全警告? (泛型)

为啥我会收到带有 std::bind() 的“本地临时返回地址”警告?

为啥在 C# 10 中我会在初始化属性上收到编译器警告/错误 CS8618

为啥我会收到警告:当 mysql-python 安装成功时,找不到与“MANIFEST”、“ChangeLog”、“GPL”匹配的文件

为啥我会从“double”转换为“float”,可能会丢失数据警告?(c)

为啥 C 没有无符号浮点数?