最近,我在我的web应用程序中遇到了这个错误:

java.lang.OutOfMemoryError:永久生成空间

它是一个典型的Hibernate/JPA + IceFaces/JSF应用程序,运行在Tomcat 6和JDK 1.6上。 显然,这可能发生在重新部署应用程序几次之后。

是什么原因导致的,如何避免呢? 我该如何解决这个问题?


当前回答

我遇到了完全相同的问题,但不幸的是,建议的解决方案都不适合我。这个问题在部署过程中没有发生,我也没有进行任何热部署。

在我的例子中,这个问题每次都发生在我的web应用程序执行期间的同一时间点,同时(通过hibernate)连接到数据库。

这个链接(前面也提到过)提供了足够的内部组件来解决这个问题。将jdbc-(mysql)-driver从WEB-INF移到jre/lib/ext/文件夹中似乎已经解决了这个问题。这不是理想的解决方案,因为升级到更新的JRE需要重新安装驱动程序。 另一个可能导致类似问题的候选是log4j,因此您可能也想移动它

其他回答

我有我们在这里谈论的问题,我的场景是eclipse-helios + tomcat + jsf,而您所做的是将一个简单的应用程序部署到tomcat。我在这里展示了同样的问题,解决方法如下。

在eclipse转到服务器选项卡双击注册服务器在我的情况下tomcat 7.0,它打开我的文件服务器一般注册信息。在“一般信息”部分单击“打开启动配置”链接,这将打开在虚拟机参数参数选项卡中添加的服务器选项的执行,最后添加这两个条目

-XX: MaxPermSize = 512m
-XX: PermSize = 512m

和准备好了。

Jrockit也帮我解决了这个问题;但是,我注意到servlet的重启时间要短得多,所以虽然在生产环境中效果更好,但在开发过程中却有点拖后腿。

First step in such case is to check whether the GC is allowed to unload classes from PermGen. The standard JVM is rather conservative in this regard – classes are born to live forever. So once loaded, classes stay in memory even if no code is using them anymore. This can become a problem when the application creates lots of classes dynamically and the generated classes are not needed for longer periods. In such a case, allowing the JVM to unload class definitions can be helpful. This can be achieved by adding just one configuration parameter to your startup scripts:

-XX:+CMSClassUnloadingEnabled

默认情况下,它被设置为false,因此要启用它,您需要显式地在Java选项中设置以下选项。如果你启用了CMSClassUnloadingEnabled, GC也会扫描PermGen并删除不再使用的类。请记住,此选项仅在UseConcMarkSweepGC也使用下面的选项启用时才有效。因此,当运行ParallelGC或串行GC时,请确保您已经通过指定将GC设置为CMS:

-XX:+UseConcMarkSweepGC

“他们”是错误的,因为我运行的是6.0.29,即使设置了所有选项也有同样的问题。正如蒂姆·豪兰所说,这些选择只会推迟不可避免的事情。它们允许我在遇到错误之前重新部署3次,而不是每次重新部署。

在部署和取消部署一个复杂的web应用程序时,我也遇到过这个问题,我想我应该加上一个解释和我的解决方案。

当我在Apache Tomcat上部署一个应用程序时,会为该应用程序创建一个新的ClassLoader。然后使用ClassLoader加载所有应用程序的类,在取消部署时,一切都应该很好地消失。然而,在现实中,事情并没有那么简单。

在web应用程序生命周期中创建的一个或多个类持有一个静态引用,该引用在某个地方引用ClassLoader。由于引用最初是静态的,所以再多的垃圾收集也不会清理这个引用——ClassLoader和它所加载的所有类都留在这里。

在几次重新部署之后,我们遇到了OutOfMemoryError。

现在这已经成为一个相当严重的问题。我可以确保在每次重新部署后重新启动Tomcat,但这会使整个服务器停机,而不仅仅是重新部署的应用程序,这通常是不可行的。

因此,我用代码组合了一个解决方案,它可以在Apache Tomcat 6.0上工作。我没有在任何其他应用服务器上进行测试,并且必须强调,如果不进行修改,在任何其他应用服务器上都很可能无法工作。

我还想说,就我个人而言,我讨厌这段代码,如果现有代码可以更改为使用适当的关闭和清理方法,那么任何人都不应该使用这段代码作为“快速修复”。只有当您的代码所依赖的外部库(在我的例子中,它是RADIUS客户端)不提供清理其自身静态引用的方法时,才应该使用这种方法。

不管怎样,继续写代码。这应该在应用程序被取消部署时调用——例如servlet的destroy方法或(更好的方法)ServletContextListener的contextDestroyed方法。

//Get a list of all classes loaded by the current webapp classloader
WebappClassLoader classLoader = (WebappClassLoader) getClass().getClassLoader();
Field classLoaderClassesField = null;
Class clazz = WebappClassLoader.class;
while (classLoaderClassesField == null && clazz != null) {
    try {
        classLoaderClassesField = clazz.getDeclaredField("classes");
    } catch (Exception exception) {
        //do nothing
    }
    clazz = clazz.getSuperclass();
}
classLoaderClassesField.setAccessible(true);

List classes = new ArrayList((Vector)classLoaderClassesField.get(classLoader));

for (Object o : classes) {
    Class c = (Class)o;
    //Make sure you identify only the packages that are holding references to the classloader.
    //Allowing this code to clear all static references will result in all sorts
    //of horrible things (like java segfaulting).
    if (c.getName().startsWith("com.whatever")) {
        //Kill any static references within all these classes.
        for (Field f : c.getDeclaredFields()) {
            if (Modifier.isStatic(f.getModifiers())
                    && !Modifier.isFinal(f.getModifiers())
                    && !f.getType().isPrimitive()) {
                try {
                    f.setAccessible(true);
                    f.set(null, null);
                } catch (Exception exception) {
                    //Log the exception
                }
            }
        }
    }
}

classes.clear();