关于异常:处理“java.lang.OutOfMemoryError:PermGen space”错误

Dealing with “java.lang.OutOfMemoryError: PermGen space” error

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

java.lang.OutOfMemoryError: PermGen space

它是一个典型的hibernate/jpa+icefaces/jsf应用程序,运行在Tomcat6和JDK1.6上。显然,这可能在重新部署应用程序几次之后发生。

是什么导致了它?怎样才能避免它?如何解决问题?


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

1
-XX:+CMSClassUnloadingEnabled -XX:+CMSPermGenSweepingEnabled

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

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

1
tomcat6w //ES//servicename

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

资料来源:ORX对埃里克敏捷回答的评论。


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

我不知道这个内存池的确切用途,但它必须与加载到JVM中的类的数量有关。(因此,为Tomcat启用类卸载可以解决这个问题。)如果您的应用程序在运行时生成并编译类,那么它更可能需要比默认值更大的内存池。


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

参见Classloader Leaks:可怕的"java.lang.outofMemoryError:PermGen Space"异常,尤其是它的后续文章。


人们常犯的错误是认为堆空间和permgen空间是一样的,这根本不是真的。堆中可能还有很多剩余空间,但PermGen中的内存仍然可能不足。

PermGen内存不足的常见原因是类加载器。每当一个类加载到JVM中时,它的所有元数据以及类加载器都保存在PermGen区域中,当加载它们的类加载器准备好进行垃圾收集时,它们将被垃圾收集。如果类加载器有一个内存泄漏,那么它加载的所有类都将保留在内存中,并在您重复几次后导致PermGen内存不足。经典的例子是Java.Lang.OutOfMeMyLogError:Tomcat中的PrimGEN空间。

现在有两种方法可以解决这个问题:1。查找内存泄漏的原因或是否存在内存泄漏。2。使用jvm参数-XX:MaxPermSize-XX:PermSize增加permgen空间的大小。

您还可以在Java中查看JavaLang.OutOfMeMyTyRebug的2种解决方案。


使用命令行参数-XX:MaxPermSize=128m替换sun jvm(显然,用128替换您需要的任何大小)。


试试-XX:MaxPermSize=256m,如果继续,试试-XX:MaxPermSize=512m


我在使用Eclipse IDE时,将-XX: MaxPermSize = 128m添加到了vm参数中(您可以实验哪个最有效)。在大多数JVM中,默认的Permsize大约是64MB,如果项目中有太多的类或大量的字符串,则内存将耗尽。

对于Eclipse,答案中也描述了它。

步骤1:双击"服务器上的Tomcat服务器"选项卡

enter image description here

步骤2:打开launch conf,将-XX: MaxPermSize = 128m添加到现有VM参数的末尾。

enter image description here


在部署和取消部署复杂的Web应用程序时,我也一直在努力解决这个问题,我想我应该添加一个解释和解决方案。

当我在ApacheTomcat上部署应用程序时,会为该应用程序创建一个新的类加载器。然后,类加载器被用来加载应用程序的所有类,在取消部署时,一切都会很好地消失。然而,事实上,这并不那么简单。

在Web应用程序的生命周期中创建的一个或多个类拥有一个静态引用,该引用在该行的某个地方引用了类加载器。由于引用最初是静态的,所以任何垃圾收集都不会清除这个引用——类加载器和它加载的所有类都将保留在这里。

在两次重新部署之后,我们遇到了内存不足的错误。

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

所以我用代码编写了一个解决方案,它可以在ApacheTomcat6.0上运行。我没有在任何其他应用服务器上进行测试,必须强调,如果不在任何其他应用服务器上进行修改,这很可能无法工作。

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
//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();

或者,您可以切换到jrockit,它处理PermGen的方式与Sun的JVM不同。它通常也有更好的性能。

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


1)增加permgen内存大小

我们可以做的第一件事是使永久一代堆空间的大小变大。这不能用通常的XMS(SET初始堆大小)和Xmx(设置最大堆大小)JVM参数来完成,因为正如上面提到的,永久生成堆空间与普通Java堆空间完全分离,这些参数为这个规则的Java堆空间设置了空间。但是,也有类似的参数可以用来(至少对于Sun/OpenJDK JVM)使永久代堆的大小变大:

1
 -XX:MaxPermSize=128m

默认值为64米。

2)启用清扫

另一个好办法是允许类被卸载,这样你的permgen就不会用完:

1
-XX:+CMSClassUnloadingEnabled -XX:+CMSPermGenSweepingEnabled

像这样的东西在过去对我有魔力。不过,有一件事是,在使用它们时会有一个显著的性能权衡,因为PermGen扫描会对您所做的每一个请求或这些行中的某些内容发出额外的2个请求。你需要平衡你的使用和权衡。

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

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


现在最简单的答案是使用Java 8。

它不再专门为permgen空间保留内存,允许permgen内存与常规内存池混合。

请记住,如果你不想让Java 8抱怨他们没有做任何事情,那么你将不得不删除所有非标准EDCOX1×2的JVM启动参数。


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

在Eclipse的"转到服务器"选项卡中,双击已注册的服务器(在我的例子中是Tomcat7.0),它将打开我的文件服务器常规注册信息。在"常规信息"部分中,单击链接"打开启动配置",这将在末尾添加的VM参数的"参数"选项卡中打开服务器选项的执行。

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

准备好了。


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

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

Java内存被分成不同的区域,在下面的图像中可以看到:

enter image description here

元空间:新的记忆空间诞生

JDK8热点JVM现在使用本机内存来表示类元数据,称为元空间,类似于OracleJrockit和IBMJVM。

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


  • 从Tomcat的bin目录中打开Tomcat7w或在"开始"菜单中键入monitor-tomcat(将打开一个带有各种服务信息的选项卡式窗口)。
  • 在Java选项文本区域中附加这行:

    1
    -XX:MaxPermSize=128m
  • 将初始内存池设置为1024(可选)。
  • 将最大内存池设置为1024(可选)。
  • 单击确定。
  • 重新启动Tomcat服务。

  • PermGen空间错误是由于使用大空间而不是由JVM提供的空间来执行代码而导致的。在UNIX操作系统中,解决这个问题的最佳方案是更改bash文件上的一些配置。以下步骤解决问题。

    在终端上运行命令gedit .bashrc

    使用以下值创建JAVA_OTPS变量:

    1
    export JAVA_OPTS="-XX:PermSize=256m -XX:MaxPermSize=512m"

    保存bash文件。在终端上运行命令exec bash。重新启动服务器。

    我希望这种方法能解决你的问题。如果使用低于8的Java版本,有时会出现此问题。但是如果使用Java 8,问题永远不会发生。


    另外,如果您在webapp中使用log4j,请在log4j文档中检查此段落。

    如果您使用的是PropertyConfigurator.configureAndWatch("log4j.properties"),则在取消部署webapp时会导致内存泄漏。


    如果存在真正的内存泄漏,增加永久生成大小或调整GC参数将不会有帮助。如果您的应用程序或它使用的某些第三方库泄漏类加载器,唯一真正和永久的解决方案是找到并修复此泄漏。有很多工具可以帮助您,最近的一个工具是Plumber,它刚刚发布了一个具有所需功能的新版本。


    Jrockit也为我解决了这个问题;但是,我注意到servlet重启时间更糟,所以虽然它在生产中更好,但在开发中有点拖累。


    我结合了Hibernate+EclipseRCP,尝试使用-XX:MaxPermSize=512m-XX:PermSize=512m,它似乎对我有用。


    设置-XX:PermSize=64m -XX:MaxPermSize=128m。稍后您还可以尝试增加MaxPermSize。希望能成功。对我来说也一样。只设置了MaxPermSize对我来说不起作用。


    我试了几个答案,最后唯一能做的就是在POM中为编译器插件进行配置:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        maven-compiler-plugin</artifactId>
        <version>2.3.2</version>
        <configuration>
            <fork>true</fork>
            <meminitial>128m</meminitial>
            <maxmem>512m</maxmem>
            <source>1.6</source>
            <target>1.6</target>
            <!-- prevent PermGen space out of memory exception -->
            <!-- -Xmx512m -XX:MaxPermSize=512m</argLine> -->
        </configuration>
    </plugin>

    希望这个能帮上忙。


    他们说最新版本的Tomcat(6.0.28或6.0.29)可以更好地处理重新部署servlet的任务。


    我遇到了完全相同的问题,但不幸的是,所有建议的解决方案都没有真正适用于我。这个问题在部署期间没有发生,我也没有进行任何热部署。

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

    这个链接(也在前面提到)提供了足够的内部信息来解决问题。把jdbc-(mysql)驱动程序从WEB-INF中移出,放到jre/lib/ext/folder中似乎已经解决了这个问题。这不是理想的解决方案,因为升级到较新的JRE需要重新安装驱动程序。另一个可能导致类似问题的候选者是log4j,所以您也可能希望移动它。


    在这种情况下,第一步是检查是否允许GC从PermGen中卸载类。在这方面,标准的JVM是相当保守的——类是生来就永远存在的。因此,一旦加载了类,即使不再有代码在使用它们,它们也会留在内存中。当应用程序动态创建大量类,并且生成的类在较长时间内不需要时,这可能会成为一个问题。在这种情况下,允许JVM卸载类定义可能会有所帮助。这可以通过在启动脚本中只添加一个配置参数来实现:

    1
    -XX:+CMSClassUnloadingEnabled

    默认情况下,此设置为false,因此使您需要在Java选项中显式设置以下选项。如果启用cmsclassUnloadingEnabled,GC也将清除PermGen并删除不再使用的类。请记住,只有在使用下面的选项启用了useConcMarkSweepGC时,此选项才有效。因此,在运行parallelgc或serial gc时,请确保已通过指定以下内容将gc设置为cms:

    1
    -XX:+UseConcMarkSweepGC


    为Tomcat分配更多内存不是正确的解决方案。

    正确的解决方案是在销毁并重新创建上下文(热部署)之后进行清理。解决方法是阻止内存泄漏。

    如果您的Tomcat/WebApp服务器告诉您注销驱动程序(JDBC)失败,则注销它们。这将阻止内存泄漏。

    您可以创建servletContextListener并在web.xml中配置它。以下是servletContextListener示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    import java.sql.Driver;
    import java.sql.DriverManager;
    import java.sql.SQLException;
    import java.util.Enumeration;

    import javax.servlet.ServletContextEvent;
    import javax.servlet.ServletContextListener;

    import org.apache.log4j.Logger;

    import com.mysql.jdbc.AbandonedConnectionCleanupThread;

    /**
     *
     * @author alejandro.tkachuk / calculistik.com
     *
     */
    public class AppContextListener implements ServletContextListener {

        private static final Logger logger = Logger.getLogger(AppContextListener.class);

        @Override
        public void contextInitialized(ServletContextEvent arg0) {
            logger.info("AppContextListener started");
        }

        @Override
        public void contextDestroyed(ServletContextEvent arg0) {
            logger.info("AppContextListener destroyed");

            // manually unregister the JDBC drivers
            Enumeration<Driver> drivers = DriverManager.getDrivers();
            while (drivers.hasMoreElements()) {
                Driver driver = drivers.nextElement();
                try {
                    DriverManager.deregisterDriver(driver);
                    logger.info(String.format("Unregistering jdbc driver: %s", driver));
                } catch (SQLException e) {
                    logger.info(String.format("Error unregistering driver %s", driver), e);
                }

            }

            // manually shutdown clean up threads
            try {
                AbandonedConnectionCleanupThread.shutdown();
                logger.info("Shutting down AbandonedConnectionCleanupThread");
            } catch (InterruptedException e) {
                logger.warn("SEVERE problem shutting down AbandonedConnectionCleanupThread:", e);
                e.printStackTrace();
            }        
        }
    }

    在这里,您可以在web.xml中配置它:

    1
    2
    3
    4
    5
    <listener>
        <listener-class>
            com.calculistik.mediweb.context.AppContextListener
        </listener-class>
    </listener>

    内存的配置取决于应用程序的性质。

    你在做什么?

    交易量是多少?

    您正在加载多少数据?

    等。

    等。

    或许你可以分析一下你的应用程序并开始清理应用程序中的一些模块。

    Apparently this can occur after redeploying an application a few times

    Tomcat有热部署,但它会消耗内存。偶尔尝试重新启动容器。此外,您还需要知道在生产模式下运行所需的内存量,这似乎是进行研究的好时机。


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


    在Eclipse IDE中,即使在设置了参数之后,也会得到这个结果。--launcher.XXMaxPermSize-XX:MaxPermSize等,如果您遇到同样的错误,则很可能Eclipse使用的是错误版本的JRE,该版本可能已由某些第三方应用程序安装并设置为默认。这些有缺陷的版本不会获取permsize参数,因此无论您设置什么,您仍然会得到这些内存错误。因此,在eclipse.ini中添加以下参数:

    1
    -vm <path to the right JRE directory>/<name of javaw executable>

    还要确保在Eclipse中的首选项中将默认JRE设置为Java的正确版本。


    唯一对我有用的方法是使用Jrockit JVM。我有MyEclipse 8.6。

    JVM堆存储由运行的Java程序生成的所有对象。Java使用EDCOX1×2×操作符来创建对象,并且在运行时在堆上分配新对象的内存。垃圾收集是自动释放程序不再引用的对象所包含的内存的机制。


    我也有类似的问题。我的是JDK7+Maven3.0.2+Struts2.0+GoogleGuice依赖注入的项目。

    每当我尝试运行mvn clean package命令时,它显示以下错误,并发生"构建失败"

    org.apache.maven.surefire.util.SurefireReflectionException: java.lang.reflect.InvocationTargetException; nested exception is java.lang.reflect.InvocationTargetException: null
    java.lang.reflect.InvocationTargetException
    Caused by: java.lang.OutOfMemoryError: PermGen space

    我尝试了以上所有有用的技巧和技巧,但不幸的是没有一个对我有用。下面一步一步地描述了为我工作的内容:=>

  • 转到pom.xml
  • 搜索maven-surefire-plugin
  • 添加一个新的元素,然后添加子元素,其中通过-Xmx512m -XX:MaxPermSize=256m元素,如下所示=>

  • -Xmx512m -XX:MaxPermSize=256m

    希望它有帮助,快乐编程:)


    您还可以通过执行以下操作来解决此问题:

    1
    rm -rf <tomcat-dir>/work/* <tomcat-dir>/temp/*

    清除工作目录和临时目录会使Tomcat做一个干净的启动。


    如果有人在NetBeans中遇到同样的错误,那么下面是我如何修复的。

    在NETBeBe:

    转到服务器上的"服务"选项卡--->右键--->选择属性--->转到虚拟机选项类型-xms1024m中的"平台"选项卡---.

    在我的例子中,我给了-xms4096米

    这是截图:

    enter image description here