题 处理“java.lang.OutOfMemoryError:PermGen space”错误


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

java.lang.OutOfMemoryError:PermGen空间

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

导致它的原因以及可以采取哪些措施来避免它? 我该如何解决这个问题?


1180
2017-09-18 03:29


起源


我已经打了好几个小时,但我没有好消息。看到我的相关问题: stackoverflow.com/questions/1996088/... 您可能仍然有内存泄漏,例如类不是垃圾收集的,因为您的WebAppClassLoader没有被垃圾收集(它有一个未清除的外部引用)。增加PermGen只会延迟OutOfMemoryError,并允许类垃圾收集是一个先决条件,但如果类加载器仍然引用它,则不会垃圾收集类。 - Eran Medan
添加时出现此错误 显示taglib。删除所以也解决了错误。为什么这样? - masT
你是怎么碰到它的? - Thorbjørn Ravn Andersen
使用JDK 1.8:þ欢迎使用MetaSpace - Rytek
如果使用Windows,请按照这些说明操作,而不是尝试在配置文件中手动设置标志。这正确地设置了Tomcat在运行时调用的值。 stackoverflow.com/questions/21104340/... - MacGyver


答案:


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

-XX:+CMSClassUnloadingEnabled -XX:+CMSPermGenSweepingEnabled

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

如果您收到错误 指定的服务不作为已安装的服务存在 你应该跑:

tomcat6w //ES//servicename

哪里 服务名称 是services.msc中查看的服务器的名称

资料来源:orx的评论 埃里克的敏捷答案


550
2017-09-17 22:23



下面的文章建议-XX:+ UseConcMarkSweepGC和-XX:MaxPermSize = 128m。 my.opera.com/karmazilla/blog/2007/03/13/... - Taylor Leese
-XX:+ CMSPermGenSweepingEnabled此选项会降低性能。这使得每个请求在我们的系统上花费的时间比平常多三倍。小心使用。 - Eldelshell
为我工作 - 谢谢 - 我在使用Tomcat6的Ubuntu 10.10上执行此操作 - 我创建了一个新文件:/usr/share/tomcat6/bin/setenv.sh并添加以下行:JAVA_OPTS =“ - Xms256m -Xmx512m - XX:+ CMSClassUnloadingEnabled -XX:+ CMSPermGenSweepingEnabled“ - 使用以下命令重启tomcat:sudo /etc/init.d/tomcat6 start - sami
在tomcat 6.0.29启动时,从我的catalina.out日志文件:“请在将来使用CMSClassUnloadingEnabled代替CMSPermGenSweepingEnabled” - knb
首先,解释这些标志真正做的事情会很棒。只是说:“做那个,享受”是不够的恕我直言。 - Nikem


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

我无法准确地使用这个内存池,但它与加载到JVM中的类的数量有关。 (因此,为tomcat启用类卸载可以解决问题。)如果应用程序在运行时生成并编译类,则更可能需要大于默认值的内存池。


247
2017-09-18 02:09



实际上,这只会推迟OOMError。请参阅下面的答案,由anon启动,其中包含两个指向frankkieviet博客的链接。 - RockyMM
这些选项在这里解释: oracle.com/technetwork/java/javase/tech/... - amos


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

看到 类加载器泄漏:可怕的“java.lang.OutOfMemoryError:PermGen space”异常,尤其是它 后续帖子


152
2018-03-11 22:24



这只是问题的真正解决方案,在某些情况下难以实施。 - RockyMM
另一个非常好的来源是 people.apache.org/~markt/presentations/... (来自Tomcat发布经理!!)。 - gavenkoa
虽然理论上这可以回答这个问题, 这会更好 在这里包括答案的基本部分,并提供参考链接。 - Joachim Sauer


人们常犯的错误是认为堆空间和permgen空间是相同的,这根本不是真的。您可能在堆中剩余大量空间但仍可能在permgen中耗尽内存。

PermGen中OutofMemory的常见原因是ClassLoader。每当将类加载到JVM中时,其所有元数据以及类加载器都保存在PermGen区域,并且当加载它们的类加载器准备好进行垃圾回收时,它们将被垃圾收集。在案例类中,类加载器有一个内存泄漏,它所加载的所有类都将保留在内存中,并且一旦重复几次就会导致内存耗尽。经典的例子是 Java.lang.OutOfMemoryError:Tomcat中的PermGen空间

现在有两种方法可以解决这个问题:
1.找出内存泄漏的原因或是否有任何内存泄漏。
2.使用JVM参数增加PermGen Space的大小 -XX:MaxPermSize 和 -XX:PermSize

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


65



如何传递参数 -XX:MaxPermSize and -XX:PermSize??我找不到 catalina.bat。我的tomcat版本是 5.5.26。 - Deckard
如何找到类加载器的内存泄漏?你推荐任何工具吗? - Amit
@amit获取工具建议,请参阅社区wiki对此问题的回答。 - Barett
@Deckard进入Tomcat / bin目录并运行tomcat6w.exe。在“Java”选项卡下,将参数添加到“Java选项”框中。点击“确定” - Zeb


使用命令行参数 -XX:MaxPermSize=128m 对于Sun JVM(显然用128代替你需要的任何大小)。


39



唯一的问题是你只是在推迟不可避免的事情 - 在某些时候你也会在那里耗尽余量。这是一个非常实用的解决方案,但它并没有永久解决它。 - Tim Howland
Eclipse中也会发生同样的事情,只要你有很多动态类加载。这种类型的装载者不会被永久地处理和生活在永久的一代中 - Matt
在执行一个特别大的Hudson工作时,我的PermGen已经用完了...这为我修好了。 - HDave
@TimHowland,如果根本原因不是类加载器泄漏,那么它可以是永久修复,只是Web应用程序中的类/静态数据太多。 - Péter Török
从源代码构建jenkins / hudson时遇到与HDave相同的问题。 - louisgab


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


35



如果它仍然坚持尝试 XX:MaxPermSize=1024m :) - igo
如果它仍然存在,请尝试XX:MaxPermSize = 2048m :) - Thomas
如果它仍然存在,请重新考虑您的应用程序!或者尝试XX:MaxPermSize = 4096m :) - jon.airey
你也可以尝试8192米,但这有点矫枉过正 - Prozi
确实有点过分 - 640KB对任何人都应该足够了! - Joel Purra


一世 添加  -XX: MaxPermSize = 128m (你可以尝试哪种方法效果最好) VM参数 因为我正在使用eclipse ide。在大多数JVM中, 默认PermSize 在附近 64MB 如果项目中有太多类或大量字符串,则内存不足。

对于日食,它也在描述 回答

步骤1 :双击tomcat服务器 服务器 标签

enter image description here

第2步 : 打开发布会 并添加 -XX: MaxPermSize = 128m 到现有的结束 VM争论

enter image description here


24



感谢您提供最详细的答案(注意:单击“打开启动配置”以打开“编辑配置”窗口...但我使用了以下参数:“ - XX:+ CMSClassUnloadingEnabled -XX:+ CMSPermGenSweepingEnabled” - Chris Sim


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

22





或者,您可以切换到JRockit,其处理permgen与sun的jvm不同。它通常也有更好的性能。

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


15



虽然JRockit确实没有PermGen,但从长远来看这无济于事。你会得到 java.lang.OutOfMemoryError: There is insufficient native memory 代替。 - stracktracer


1)增加PermGen内存大小

人们可以做的第一件事就是使永久代堆空间的大小更大。使用通常的-Xms(设置初始堆大小)和-Xmx(设置最大堆大小)JVM参数无法做到这一点,因为如上所述,永久生成堆空间完全独立于常规Java堆空间, 这些参数为这个常规Java堆空间设置了空间。但是,有类似的参数可以使用(至少使用Sun / OpenJDK jvms)来使永久代堆的大小更大:

 -XX:MaxPermSize=128m

默认值为64米。

2)启用扫描

另一种妥善处理的方法是允许卸载类,以便PermGen永远不会耗尽:

-XX:+CMSClassUnloadingEnabled -XX:+CMSPermGenSweepingEnabled

这样的东西在过去对我有用。但有一件事,在使用这些产品时会有显着的性能折衷,因为每次扫描会对您提出的每个请求或类似的内容提出额外的2个请求。您需要在使用和权衡之间取得平衡。

您可以找到此错误的详细信息。

http://faisalbhagat.blogspot.com/2014/09/java-outofmemoryerror-permgen.html


14



@faisalbhagat的伟大帖子 faisalbhagat.blogspot.com/2014/09/... - meurer
选项2很棒,但只是警告它不应该在生产环境中使用。通常最好只将其保留在开发环境中。但是,从Java 8开始删除PermGen openjdk.java.net/jeps/122 - hdost


我遇到了我们在这里讨论的问题,我的场景是eclipse-helios + tomcat + jsf,你正在做的是将一个简单的应用程序部署到tomcat。我在这里显示了同样的问题,解决方法如下。

在日食中去 服务器 选项卡双击我的案例tomcat 7.0中的注册服务器,它打开我的文件服务器常规注册信息。在该部分 “一般信息” 点击链接 “开放发布配置” ,这将在最后添加这两个条目的VM参数的Arguments选项卡中打开服务器选项的执行

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

准备好了。


13