处理 “java.lang.OutOfMemoryError:PermGen 空间” 错误

最近,我在 Web 应用程序中遇到此错误:

java.lang.OutOfMemoryError:PermGen 空间

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

是什么原因引起的,可以采取什么措施避免它发生?我该如何解决该问题?

答案

解决方案是在启动 Tomcat 时将这些标志添加到 JVM 命令行:

-XX:+CMSClassUnloadingEnabled -XX:+CMSPermGenSweepingEnabled

您可以通过关闭 tomcat 服务,然后进入 Tomcat / bin 目录并运行 tomcat6w.exe 来做到这一点。在 “Java” 选项卡下,将参数添加到 “Java 选项” 框中。单击 “确定”,然后重新启动服务。

如果出现错误, 则指定的服务不存在为已安装的服务 ,应运行:

tomcat6w //ES//servicename

其中servicename是在 services.msc 中查看的服务器的名称

资料来源:orx 对Eric 的《敏捷答案》的评论。

您最好尝试-XX:MaxPermSize=128M而不是-XX:MaxPermGen=128M

我无法告诉您该内存池的确切用途,但它与装入 JVM 的类数有关。 (因此,为 tomcat 启用类卸载可以解决该问题。)如果您的应用程序在运行时生成并编译类,则很有可能需要比默认更大的内存池。

多次部署后发生的应用程序服务器 PermGen 错误很可能是由容器保存到旧应用程序的类加载器中的引用引起的。例如,使用自定义日志级别类将导致引用由应用服务器的类加载器保留。您可以使用 jmap 和 jhat 等现代(JDK6 +)JVM 分析工具检测这些类加载器之间的泄漏,以查看哪些类继续保留在您的应用程序中,然后重新设计或取消使用它们。通常的可疑对象是数据库,记录器和其他基础框架级别的库。

请参阅Classloader 泄漏:可怕的 “java.lang.OutOfMemoryError:PermGen 空间” 异常 ,尤其是其后续文章

人们常犯的错误是认为堆空间和 permgen 空间是相同的,这是完全不正确的。您可能在堆中剩余很多空间,但仍可能在 permgen 中用完内存。

PermGen 中 OutofMemory 的常见原因是 ClassLoader。每当将类加载到 JVM 中时,所有其元数据以及 Classloader 都将保留在 PermGen 区域上,并且在将它们加载到的 Classloader 准备好进行垃圾收集时,将对它们进行垃圾收集。在 Case Case 发生内存泄漏的情况下,一旦重复几次,它加载的所有类将保留在内存中并导致 permGen 内存不足。经典示例是Tomcat 中的 Java.lang.OutOfMemoryError:PermGen Space

现在有两种方法可以解决此问题:
1. 查找内存泄漏的原因或是否有内存泄漏。
2. 通过使用 JVM 参数-XX:MaxPermSize-XX:PermSize来增加 PermGen 空间的大小。

您也可以在 Java 中查看2 Java.lang.OutOfMemoryError 的解决方案以获取更多详细信息。

将命令行参数-XX:MaxPermSize=128m用于 Sun JVM(显然,将 128 替换为所需的任何大小)。

尝试-XX:MaxPermSize=256m ,如果仍然存在,请尝试-XX:MaxPermSize=512m

我在使用 eclipse ide 时向VM 参数 添加了 -XX: MaxPermSize = 128m (您可以尝试哪种方法效果最好)。在大多数 JVM 中, 默认的 PermSize约为64MB ,如果项目中有太多的类或太多的 String,则会耗尽内存。

对于日食,也可以在answer 上进行描述。

步骤 1 :在 “ 服务器”选项卡上双击 tomcat 服务器

在此处输入图片说明

步骤 2打开 Conf,并在现有VM 论据的末尾添加-XX: MaxPermSize = 128m

在此处输入图片说明

在部署和取消部署复杂的 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();

java.lang.OutOfMemoryError: PermGen空间消息指示内存中的永久世代区域已耗尽。

允许任何 Java 应用程序使用有限数量的内存。在应用程序启动期间,将指定特定应用程序可以使用的确切内存量。

Java 内存分为不同的区域,如下图所示:

在此处输入图片说明

元空间:新的内存空间诞生了

现在,JDK 8 HotSpot JVM 使用本机内存来表示类元数据,这称为元空间。与 Oracle JRockit 和 IBM JVM 类似。

好消息是,它不再意味着java.lang.OutOfMemoryError: PermGen空间问题,也不再需要使用Java_8_Download或更高版本来调整和监视此内存空间。

另外,您可以切换到 JRockit,它对 permgen 的处理方式与 sun 的 jvm 不同。通常它也具有更好的性能。

http://www.oracle.com/technetwork/middleware/jrockit/overview/index.html