7

在我工作的地方,我们的一个应用程序中遇到了 JVM 堆空间不足的问题。我已经对此进行了一些搜索,包括使用分析器查看堆转储,但现在我几乎陷入困境。

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

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

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

然后,我试图找到造成这种情况的原因,但这要困难得多。由于 Hibernate 加载的对象将与它们的会话寿命一样长,我尝试通过将 Spring 的调用替换HibernateDaoSupport#getSession()HibernateDaoSupport#getSessionFactory().getCurrentSession(). 这对问题没有明显的影响。我还尝试添加对 ... 的调用,getCurrentSession().flush()并且.clear()在一些有问题的道方法的 finally-block 中,也没有明显的效果。(Dao-methods都是用 注释的@Transactional,这应该意味着一个会话应该只在@Transactional-method内是活跃的,并且对该方法的连续调用应该在调用时获得不同的会话getCurrentSession()(?))

所以,现在我在提出其他要检查的领域时几乎被卡住了。有没有人有关于在哪里寻找和寻找什么的想法或一些指示?

堆转储显示有很多实例org.hibernate.impl.SessionFactoryImpl,这是否符合预期?(我原以为应该只有一个 SessionFactory 实例,或者几个顶部。)

编辑:

我想我实际上已经设法解决了这个问题:

事实证明,在 webservice-classes 中处理对其他对象的依赖是问题所在。这是通过ClassPathXmlApplicationContext(...)在 web 服务类的构造函数中调用 new 来解决的。这导致为每个请求(或至少每个会话)加载大量对象,而这些对象没有再次卸载(主要是 Hibernate 的SessionFactoryImpl)。我已经更改了 webservice-classes,因此它们注入了它们的依赖项,并形成了我迄今为止使用分析器看到的内容,多个SessionFactoryImpl-objects 的问题已经解决。

我认为从 GlassFish 2.x 升级到 GlassFish 3.x 可能会加剧问题,这可能是关于如何实例化 webservice-classes 的一些差异。

4

1 回答 1

5

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

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

新的 ClassPathXmlApplicationContext(...)

在各个 web 服务类中。这样做会产生讨厌的副作用,即加载的对象避免被垃圾收集(我猜是因为它们被 Spring 的一些内部组件引用)。似乎 glassfish 版本的更改对 webservice-objects 的实例化做了一些事情,导致对 new this 的调用更多,因此更多的垃圾对象占用内存,直到它填满并崩溃。

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

新的 ClassPathXmlApplicationContext(...)

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

public class ContextHolder {
    private static ClassPathXmlApplicationContext context;

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

并在 webservice-classes 中调用它,而不是 new-ing ClassPathXmlApplicationContext。

更新:

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

try (final ClassPathXmlApplicationContext applicationContext =
             new ClassPathXmlApplicationContext("applicationContext.xml")) {
    //do stuff
}
于 2012-12-07T13:52:57.777 回答