Hibernate,SessionFactoryObjectFactory和OutOfMemoryError:java堆空间

时间:2012-03-05 10:10:00

标签: hibernate memory-leaks out-of-memory sessionfactory

在我工作的地方,我们遇到了JVM在我们的一个应用程序中耗尽堆空间的问题。我已经做了一些寻找原因,包括用分析器查看堆转储,但现在我几乎被卡住了。

首先,关于有问题的系统:它是一个使用Spring和Hibernate的Java应用程序来保存有关组织的记录。该系统由一组Web服务客户端组成,用于从负责此类数据的政府机构中检索有关组织的数据。此外,系统使用此类数据保留本地数据库,充当Web服务调用的缓存,以便第一次请求有关组织的信息,将其保存在本地关系数据库中,并将其用于检索以下请求的数据。 Hibernate用于与此数据库通信。

如前所述,问题是在一段时间后,应用程序开始与OutOfMemoryError崩溃:java堆空间。我已经使用Eclipse + MAT查看了堆转储,并将罪魁祸首确定为Hibernate的SessionFactoryObjectFactory,占用了大约85%的已分配内存(全部保留了内存)。我发现确切地确定在其中保留了什么类型的对象有点困难。在顶层有Glassfish WebappClassLoader,其中包含org.hibernate.impl.SessionFactoryObjectFactory。其中包含org.hibernate.util.FastHashMap,其中包含java.util.HashMap。它包含许多条目,每个条目包含一个HashMap条目,一个org.hibernate.impl.SessionFactoryImpl和一个String。 HashMap条目依次包含相同的三个对象,一个HashMap条目,一个SessionFactoryImpl和一个String,这个结构重复了很多次。 SessionFactoryImpl包含许多对象,最值得注意的是org.hibernate.persister.entity.SingleTableEntityPersister,其中包含许多字符串和HashMaps。一些字符串引用域对象中的变量,一些字符串包含sql语句。

乍一看,这个对象占用了不必要的内存量(转储文件为800MB,其中650MB由SessionFactoryObjectFactory占用),因此我启用了对象加载和卸载的日志记录,以及尝试向系统询问有关组织的数据(通过来自其他系统的Web服务调用)。我在这里注意到的是有很多用于加载对象的消息,但很少有关于卸载对象的消息(只有那些卸载了库对象)。这让我相信,一旦一个对象(比如一个组织)被加载到内存中,它就永远不会被卸载,这意味着随着时间的推移,系统将耗尽内存。 (这是基于日志中发现的内容的公平假设吗?)

然后,我试着找到原因,但这要困难得多。由于Hibernate加载的对象只要会话存在就会存在,我尝试通过将对Spring的HibernateDaoSupport#getSession()的调用替换为HibernateDaoSupport#getSessionFactory().getCurrentSession()来改变会话的处理方式。这对问题没有任何影响。我还尝试在一些Dao方法的finally块中添加对getCurrentSession().flush().clear()的调用,也没有任何影响。 (Dao方法都注明了@Transactional,这意味着会话应该只在@Transactional - 方法中存活,并且在调用{{1}时,对方法的连续调用应该会得到不同的会话}(?))

所以,现在我在提出其他方面检查方面几乎陷入困境。有没有人有关于在哪里寻找和寻找什么的想法或指针?

堆转储表明有很多getCurrentSession()的实例,这是否符合预期? (我原本以为应该只有一个SessionFactory实例,或者几个tops。)

编辑:

我想我实际上是为了解决这个问题:

事实证明,在webservice-classes中处理对其他对象的依赖是一个问题。这是通过在webservice类的构造函数中调用新的org.hibernate.impl.SessionFactoryImpl来解决的。这导致为每个请求(或至少每个会话)加载了很多对象,这些对象没有再次卸载(主要是Hibernate的ClassPathXmlApplicationContext(...))。我已经改变了webservice-classes,所以他们注入了他们的依赖,并形成了我迄今为止使用的探查器,多个SessionFactoryImpl - 对象的问题已经解决了。

我认为从GlassFish 2.x升级到GlassFish 3.x可能会使问题恶化,可能在如何实例化webservice-classes方面存在一些差异。

1 个答案:

答案 0 :(得分:5)

我不妨在答案中添加这个问题的解决方案,而不仅仅是问题本身:

这里的罪魁祸首是如何在各种对象中加载spring-beans,最明显的是在webservice类中。这是通过调用

来完成的
  

new ClassPathXmlApplicationContext(...)

在各个webservice-classes中。这样做有加载对象的令人讨厌的副作用,避免被垃圾收集(我想因为它们被一些Spring的内部引用)。似乎glassfish版本的变化对webservice-objects的实例化做了一些事情,导致调用更多对new的调用,因此更多的垃圾对象占用内存,直到它填满并崩溃。

解决问题的方法是将呼叫转移到

  

new ClassPathXmlApplicationContext(...)

使用静态工厂模式进入另一个类,如下所示:

public class ContextHolder {
    private static ClassPathXmlApplicationContext context;

    public static getSpringContext() {
        if (context == null) {
            context = new ClassPathXmlApplicationContext("applicationContext.xml");
        }
        return context;
    }
}

在webservice-classes中调用它,而不是新建的ClassPathXmlApplicationContext。

更新

ClassPathXmlApplicationContextCloseable / Autocloseable,因此try-with-resource是另一种可能性:

try (final ClassPathXmlApplicationContext applicationContext =
             new ClassPathXmlApplicationContext("applicationContext.xml")) {
    //do stuff
}