【问题标题】:Garbage collection of spring container弹簧容器的垃圾收集
【发布时间】:2012-05-15 21:48:22
【问题描述】:

我在我的应用程序中使用 Spring 框架(版本 3.0.3)。最近,我遇到了这个讨厌的java.lang.OutOfMemoryError: Java heap space 错误。该错误不是在执行时立即出现,而是在应用程序运行几个小时后出现。在那之前,应用程序会运行得非常好,然后突然间 jvm 会因为内存不足而崩溃。
经过广泛调查,我认为这个问题与 Spring 有关。我注意到所有的类,只要它们需要注入一个 bean,它们就会创建一个 XMLBeanFactory 的新实例。也就是说,他们一开始都有这样的代码:

XmlBeanFactory beanfactory = new XmlBeanFactory(new ClassPathResource("SpringConfig.xml"));
Bean myBean = beanfactory.getBean("MyBean");

我知道不建议这样做。您只需要一个 Spring 容器实例,并为所有 bean 创建请求引用该实例。所以我通过单例实现了 SpringFactory,从而始终创建 XMLBeanFactory 的单个实例。
进行上述更改似乎已经解决了内存泄漏问题!
我还不能决定:

  1. 即使我使用多个 spring 容器实例来获取 bean,当容器超出范围时,它应该释放所有 bean 引用,使所有 bean 可用于垃圾收集。那么是什么导致了内存泄漏问题呢?正如我所提到的,仅使用单例容器会导致内存泄漏消失。如果需要,我可以提供更多详细信息。
  2. 我们之前使用多个容器的原因是 bean 不是无状态的。为了解决这个问题,在我的单例 spring 容器中,我将所有 bean 范围设置为 prototype。这种方法正确吗?

更新
在将以下代码添加到所有 Spring bean 之后,我得出了有趣的发现:

protected void finalize()
{
System.out.println(this +" object is garbage collected");
}

我通过让每个类实例化一个新的 Spring 容器然后获取 bean 来运行代码。上面的注释是为几乎所有创建的 bean 打印的。这意味着所有的 Spring bean 都被清理干净了。但是,用完的内存会随着时间的推移而增加。
当我通过让所有类使用相同的 Spring 容器来做同样的事情时,使用的内存或多或少保持稳定。这让我觉得 Spring 容器正在保存内存。
所以问题是,Spring 容器(如上述代码中获得)垃圾何时被收集?我认为一旦超出范围就有资格进行垃圾收集!
似乎 Hibernate Session Object 正在缓存资源并占用内存。我对此不确定,但堆转储分析显示 Hibernate 的“字符串”是主要的内存持有者,例如。一些字符串有 sql 查询和 hibernate 创建的别名。但我想知道 Hibernate 缓存(我没有使用二级缓存)如何以及为什么仅在我使用多个 Spring 容器时才会导致问题!
更新
最后,我能够指出是什么阻碍了记忆。它是 Hibernate 生成的主缓存。对象已被清除,因为我们有 getHibernateTemplate().clear() 。但是,每个会话都会缓存 sql 查询和休眠属性,并且每个 Spring 容器都会创建一个新会话。由于缓存应该在会话关闭时自动清除,内存增长意味着会话没有关闭。进一步验证了这一点,因为当我在 DAO 课程结束时明确执行 getHibernateTemplate().getSessionFactory().close() 时,我没有遇到任何内存问题。
所以,问题是,为什么即使容器本身超出范围,spring hibernate 模板也不会关闭会话?我没有在代码中的任何地方明确处理会话,即使我有一个正在运行的线程,问题仍然存在。让我觉得框架实现本身有问题!

【问题讨论】:

  • 这段代码错过了 IoC 容器的全部要点。您不是从工厂 bean,而是让它 bean 到您的bean 中!
  • 虽然可能给人的印象是我在拉豆子,但实际上我并没有在代码中硬编码豆子名称。我正在使用一个公共属性文件,使用它,Spring 实际上会推送 bean。
  • 这听起来更糟。 bean 名称应该在 xml 配置文件中,您的 Java 代码甚至不应该知道 IoC 容器的存在,更不用说任何 bean 的配置名称了。
  • 我想我明白你的意思。所以你的意思是说,无论如何,都应该避免做 BeanFactory.getBeans("beanName") ?但我想知道,即使在这种情况下,调用类也不知道 bean 的实现,只有 Spring 会知道在运行时要注入哪个 bean 类。我错过了什么吗?
  • 是的,就是这样。您会得到与现在相同的最终结果,但您的代码为零(除非您将 xml 配置视为代码)。

标签: java spring garbage-collection singleton out-of-memory


【解决方案1】:

你到底得到了哪个 OutOfMemoryError?我估计是java.lang.OutOfMemoryError: PermGen space

如果是这样,这里是解释:

每个 spring 容器使用不同的类加载器(但它们都有相同的父类加载器)。当一个类被加载时,它被放在永久代内存中,而不是堆中,并且默认情况下它们永远不会被 JVM 垃圾收集。使用不同的类加载器加载的同一个类被认为是不同的,因此,当您创建更多新的 Spring IoC 时,永久代会被填满,并最终用完空间给java.lang.OutOfMemoryError: PermGen space

要解决这个问题,应该为你的 JVM 启用类卸载选项:

-XX:+CMSClassUnloadingEnabled -XX:+CMSPermGenSweepingEnabled

对于其他类型的内存不足错误,我目前看不到任何解释。除非你使用线程,否则在 Java 中很难造成内存泄漏。

【讨论】:

  • 谢谢。我刚刚更新了我的问题。我收到 java.lang.OutOfMemoryError: Java heap space。而且似乎 Hibernate Session Object 正在缓存资源并占用内存。我对此不确定,但堆转储分析显示 Hibernate 的“字符串”是主要的内存持有者。
  • 它与你的代码中的线程有什么关系吗?如果是这样,stackoverflow.com/questions/1071631/…
  • 谢谢。该链接有助于进一步了解该问题。但是,我尝试调用 getHibernateTemplate.flush() 和 .clear() 似乎没有帮助。
  • 我相信每个休眠类都有一个会话集。因此,如果休眠类没有被卸载(根据我在答案中给出的原因),它们的会话也不会被垃圾收集,因为它们仍然与永久代中的类相关联。尝试使用我上面建议的选项,让我知道会发生什么。
  • 是的,问题与会话有关。但我想不是类加载。我曾尝试使用您建议的 JVM 选项,但它们没有帮助。但有什么帮助是通过“getHibernateTemplate().getSessionFactory().close()”显式关闭会话(参见更新)。似乎春天没有关闭会话!
【解决方案2】:
  1. 您的容器真的超出范围,还是在内部某个地方注册?

  2. Spring 使您的类无状态的另一点是您拥有尽可能少的实例,并在启动时根据需要加载它们。所以你的作用域应该尽可能是单例的,如果不是,你应该问自己为什么它不是单例的,我可以这样做吗。

【讨论】:

  • 谢谢斯宾塞。对于(1),我没有在任何地方明确注册容器。关于可以自动注册 Spring 容器的常见实例的任何指针?对于(2),我制作原型的 bean 是 DAO bean,将它们用作单例可能会为不同的用户产生冲突。
猜你喜欢
  • 1970-01-01
  • 2018-12-30
  • 1970-01-01
  • 2011-11-07
  • 2013-04-01
  • 2012-06-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多