解决“java.lang.OutOfMemoryError: PermGen space”错误

1242

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

java.lang.OutOfMemoryError: PermGen space

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

这是什么原因造成的?如何避免它?如何解决这个问题?


我已经为此奋斗了数小时,但是我没有好消息。请参阅我的相关问题:https://dev59.com/anI-5IYBdhLWcg3wJE0K 您可能仍然存在内存泄漏,例如类没有被垃圾回收,因为您的WebAppClassLoader没有被垃圾回收(它具有未清除的外部引用)。增加PermGen只会延迟OutOfMemoryError,并且允许类垃圾回收是一个前提条件,但如果它们的类加载器仍然有引用,则不会垃圾回收类。 - Eran Medan
我在添加display taglib时遇到了这个错误。删除它也解决了这个错误。为什么会这样? - masT
你是怎么遇到它的? - Thorbjørn Ravn Andersen
15
使用JDK 1.8:þ 欢迎来到MetaSpace。 - Rytek
如果使用Windows操作系统,请按照以下说明进行操作,而不是尝试在配置文件中手动设置标志。这样可以正确地将值设置在注册表中,在运行时由Tomcat调用。http://stackoverflow.com/questions/21104340/increase-windows-installer-based-tomcat-permgen-space - Entree
34个回答

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

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

-XX:+CMSClassUnloadingEnabled -XX:+CMSPermGenSweepingEnabled

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

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

tomcat6w //ES//servicename

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

来源:orx 在Eric's Agile Answers中的评论。


39
下面的文章建议同时使用 -XX:+UseConcMarkSweepGC 和 -XX:MaxPermSize=128m。链接如下:http://my.opera.com/karmazilla/blog/2007/03/13/good-riddance-permgen-outofmemoryerror - Taylor Leese
31
该选项会降低性能,使每个请求在我们的系统上比通常情况下需要三倍的时间。请谨慎使用。 - Eldelshell
10
对我很有用 - 谢谢 - 我正在使用 Ubuntu 10.10 和 Tomcat6 进行操作 - 我创建了一个新文件:/usr/share/tomcat6/bin/setenv.sh,然后添加以下内容:JAVA_OPTS="-Xms256m -Xmx512m -XX:+CMSClassUnloadingEnabled -XX:+CMSPermGenSweepingEnabled" - 使用以下命令重新启动 tomcat:sudo /etc/init.d/tomcat6 start - sami
24
在Tomcat 6.0.29启动时,从我的catalina.out日志文件中可以看到:“请在以后使用CMSClassUnloadingEnabled替代CMSPermGenSweepingEnabled”。 - knb
225
首先,很好地解释一下这些标志的真正作用是非常重要的。仅仅说“这样做就可以享受”在我看来并不足够。 - Nikem
显示剩余14条评论

252

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

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


9
实际上,这只是推迟OOMError的发生。请参见下面由一个匿名用户开始的答案,其中包含两个链接指向frankkieviet博客。 - Rade_303
这些选项在这里有解释: http://www.oracle.com/technetwork/java/javase/tech/vmoptions-jsp-140102.html#PerformanceTuning - amos

154

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

请参阅 Classloader leaks: the dreaded "java.lang.OutOfMemoryError: PermGen space" exception,尤其是其 后续帖子


3
这是问题唯一的真正解决方案,尽管在某些情况下难以实现。 - Rade_303
另一个非常好的资源是http://people.apache.org/~markt/presentations/2010-11-04-Memory-Leaks-60mins.pdf(来自Tomcat发布经理!!)。 - gavenkoa
22
虽然这理论上回答了问题,但最好在这里包含答案的关键部分,并提供链接作为参考。 - Joachim Sauer

69

人们常犯的一个错误是认为堆空间和永久代空间是一样的,这完全不正确。即使堆中还有很多空间可用,但仍然可能在永久代中耗尽内存。

导致 PermGen 内存溢出的常见原因是类加载器。每当一个类被加载到 JVM 中时,它的所有元数据以及 Classloader 都会保留在 PermGen 区域中,并且它们将在加载它们的 Classloader 准备进行垃圾回收时被回收。如果 Classloader 存在内存泄漏,则由它加载的所有类都将保留在内存中,并且在重复几次之后会导致 PermGen 内存不足。经典的例子是 Java.lang.OutOfMemoryError:PermGen Space in Tomcat

现在有两种解决方法:
1. 找到内存泄漏的原因或确认是否存在内存泄漏。
2. 使用 JVM 参数 -XX:MaxPermSize-XX:PermSize 来增加 PermGen 空间大小。

您还可以查看Java 中 Java.lang.OutOfMemoryError 的 2 个解决方案了解更多详情。


3
жҖҺж ·дј йҖ’еҸӮж•°-XX:MaxPermSizeе’Ң-XX:PermSizeпјҹжҲ‘жүҫдёҚеҲ°catalina.batгҖӮжҲ‘зҡ„TomcatзүҲжң¬жҳҜ5.5.26гҖӮ - Deckard
如何找到类加载器的内存泄漏?您推荐使用哪个工具? - Amit
@amit,关于工具推荐,请参考此问题的社区维基答案。 - Barett
@Deckard 进入 Tomcat/bin 目录并运行 tomcat6w.exe。在“Java”选项卡下,将参数添加到“Java 选项”框中。点击“确定”。 - Zeb

43

如果使用Sun JVM,可以使用命令行参数-XX:MaxPermSize=128m(将128替换为所需大小)。


9
唯一的问题是你只是在推迟不可避免的事情——在某个时候,你也会用尽那里的空间。这是一个很好的务实解决方案,但它并不能永久解决问题。 - Tim Howland
在Eclipse中或者任何需要大量动态类加载的情况下,同样的问题会出现。类加载器没有被处理并且永久存在于永久代中。 - Matt
1
当执行一个特别大的Hudson作业时,我遇到了PermGen不足的问题...这个方法解决了我的问题。 - Dave
10
@TimHowland,如果根本原因不是类加载器泄漏,而只是您的Web应用程序中有太多的类/静态数据,那么它可以成为固定的永久解决方案。 - Péter Török
在从源代码构建Jenkins/Hudson时,遇到了与HDave相同的问题。 - louisgab

40
尝试使用-XX:MaxPermSize=256m,如果仍然存在问题,请尝试使用-XX:MaxPermSize=512m

59
如果问题仍然存在,请尝试XX:MaxPermSize=1024m :) - igo
40
如果问题仍然存在,请尝试XX:MaxPermSize=2048m :) - Thomas
18
如果问题仍然存在,请重新考虑你的应用程序!或者尝试使用XX:MaxPermSize=4096m :) - Jonathan Airey
19
你也可以尝试8192m,但那有点过度。 - Jacek Pietal
14
确实有点过头了——640KB 应该足够任何人使用了! - Joel Purra
显示剩余2条评论

29
添加-XX: MaxPermSize = 128m(您可以尝试哪个效果最好)到 VM参数 中,因为我正在使用eclipse ide。在大多数JVM中,默认的PermSize约为 64MB ,如果项目中有太多的类或大量的字符串,它会耗尽内存。

对于eclipse,也在答案中进行了描述。

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

enter image description here

步骤2:打开启动配置文件并在现有的VM参数末尾添加-XX:MaxPermSize=128m以增加内存空间。

enter image description here


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

23

我也遇到了一个类似的问题,当我部署和卸载复杂的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();

我知道距离我写这篇文章已经过去了8年,但在那时和现在之间,我找到了一个更深层次的根本原因和解决方案。 问题在于,虽然 webapp 中的所有类都由上下文类加载器拥有,但调用启动和关闭回调的线程却由父类加载器拥有。这意味着,如果关闭代码初始化了一个线程本地变量,这将导致父类加载器持有对上下文类加载器的引用,从而防止进行良好的清理。 - Edward Torbett
幸运的是,有一个非常简单的解决办法 - 将当前在关闭方法中的所有代码移动到一个新的线程对象的运行方法中。然后在关闭方法中,启动此线程并等待其完成。清理代码将被相同地执行,但任何线程本地变量仍将绑定到上下文类加载器而不是泄漏。 - Edward Torbett

20

java.lang.OutOfMemoryError: PermGen空间消息表示内存中Permanent Generation区域已耗尽。

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

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

enter image description here

Metaspace:一个新的内存空间诞生了

JDK 8 HotSpot JVM现在使用本机内存来表示类元数据,并称为Metaspace;类似于Oracle JRockit和IBM JVM。

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


MetaSpace 也可能发生 OutOfMemory - srk

17

1)增加PermGen内存大小

首先可以做的是增加永久代堆空间的大小。这无法使用通常的 -Xms(设置初始堆大小)和 -Xmx(设置最大堆大小)JVM参数来完成,因为正如提到的那样,永久代堆空间完全独立于常规的Java堆空间,而这些参数是用于设置常规Java堆空间的空间。然而,有类似的参数可以使用(至少在Sun/OpenJDK JVM中)来增加永久代堆的大小:

 -XX:MaxPermSize=128m

默认为64m。

2) 启用Sweeping

另一种永久解决方法是允许类被卸载,这样您的PermGen就不会耗尽:

-XX:+CMSClassUnloadingEnabled -XX:+CMSPermGenSweepingEnabled

类似这样的东西过去对我产生了神奇的效果。不过,使用它们存在显著的性能折衷,因为 permgen 扫描将为您的每个请求进行额外的2次请求或类似行为。 您需要在使用中权衡好利弊。

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

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


@faisalbhagat 的这篇文章非常棒 http://faisalbhagat.blogspot.com/2014/09/java-outofmemoryerror-permgen.html - leomeurer
选项2很好,但请注意不应在生产环境中使用。通常最好仅在开发环境中使用。然而,自Java 8起已删除PermGen。http://openjdk.java.net/jeps/122 - hdost

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接