【发布时间】:2014-12-02 17:32:42
【问题描述】:
我正在切换应用程序以使用 spring 并在运行时动态加载一些类。当通过 maven 运行我们基于 JUnit 的测试套件时,此更改会导致一致的“java.lang.OutOfMemory: PermGen Space”错误。我们大约有800个测试,OoM被抛出了大约200个测试。代码更改如下:
private Obj buildMyObj() {
//Obj obj = MyObj(1); //Old method to get obj
ObjInterface obj = (ObjInterface) MyAppContext.INSTANCE.getApplicationContenxt().getBean("Obj");
}
并且在 xml 文件中,我添加了以下内容:
<bean id="Obj" class="com.project.obj.ObjImplOne">
<constructor-arg name="arg0" val="1"/>
</bean>
buildMyObj() 方法在每个单元测试中大约运行一次,尽管有些人会调用它很多次。前一种方法,直接声明对象(在我们转向接口方法之前)工作得很好。但是,既然我们已经有了一个接口,并且可能在运行时使用了几种不同的实现,我们需要在运行时加载它,而 JUnit 并没有发挥作用。
我已经跟进了一些现有的 stackoverflow 线程,似乎每当 Spring 动态加载一个类时,它都会将它放在 PermGen 中,但它不够聪明,无法将后续加载的类识别为相同并导致每次调用都会在 PermGen 中放置一个新类,因此会出现错误。建议允许使用 '-XX:+CMSClassUnloadingEnabled' JVM 选项进行类卸载(建议是 '-XX:+CMSClassUnloadingEnabled -XX:+CMSPermGenSweepingEnabled',但 JVM 抱怨“请使用 CMSClassUnloadingEnabled 代替 CMSPermGenSweepingEnabled未来”
但这并没有改变任何东西,错误仍然被抛出。
编辑:在单独的终端中观看测试套件运行和监控 top 之后,JVM 似乎没有做任何事情来回收正在加载的类。我根据建议的评论提出了 permgen 大小,而 java 只是继续要求内存更长的时间(价值超过 2 gigs)。类加载和 getBean() 有问题吗?
【问题讨论】:
-
将 perm-gen vm 参数添加到您的测试中 -> -XX:MaxPermSize=256m
-
这并没有解决这个问题 - 虽然增加的 permgen 大小导致运行更多测试,但它最终还是像以前一样停滞了
-
你的
MyAppContext.INSTANCE.getApplicationContenxt().getBean("Obj")在做什么?另请注意,您必须关闭上下文,否则它将继续消耗内存。通常使用getBean是一种代码异味/不好的做法,应该避免,应该改用依赖注入。 -
该行获取一个包含 Spring 应用程序上下文的单例,并使用它在运行时获取我们想要的特定 bean。老实说,我正在使用该代码,因为它与项目其他部分的处理方式相同(单元测试的 @BeforeClass 方法中有类似的负载),但我不是真正的 Java 人.
-
无处不在的事实并不意味着它是正确的,恕我直言,这是一种不好的做法。根据它的作用,它甚至可能不是一个单例,并且每次加载一个新的上下文都会占用内存。此外,类加载不是 Spring 的事情,它是 java 工作方式的基本组成部分(类的存储位置取决于 java 版本和/或使用的内存模型)。