解决“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个回答

4

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


3

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

正确的解决方案是在上下文被销毁和重建(热部署)后进行清理。解决方案是停止内存泄漏。

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

您可以创建一个ServletContextListener并在web.xml中进行配置。以下是一个样本ServletContextListener:

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中配置它:

<listener>
    <listener-class>
        com.calculistik.mediweb.context.AppContextListener 
    </listener-class>
</listener>  

3
我遇到了完全相同的问题,但不幸的是,建议的解决方案都没有真正为我工作。这个问题没有在部署期间发生,而且我也没有进行任何热部署。
在我的情况下,在连接(通过hibernate)到数据库时,在执行我的Web应用程序的相同点每次都会出现问题。 此链接(之前也提到过)提供了足够的见解来解决问题。将jdbc-(mysql)-driver移出WEB-INF并移到jre / lib / ext /文件夹中似乎已解决该问题。这不是理想的解决方案,因为升级到新的JRE需要重新安装驱动程序。可能会导致类似问题的另一个候选者是log4j,因此您可能还想将其移动。

如果您不想将驱动程序包含在jre/lib/ext中,您可以通过将驱动程序包含在容器启动类路径中来获得相同的结果。java -cp /path/to/jdbc-mysql-driver.jar:/path/to/container/bootstrap.jar container.Start - Scot

3

据说最新版本的Tomcat(6.0.28或6.0.29)能更好地处理重新部署Servlets的任务。


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

默认情况下,此选项设置为false。如果要启用此选项,需要在Java选项中显式设置以下选项。如果启用CMSClassUnloadingEnabled,则GC将扫描PermGen并删除不再使用的类。请记住,只有在使用以下选项启用UseConcMarkSweepGC时,此选项才起作用。因此,在运行ParallelGC或Serial GC时,请确保通过指定以下内容将GC设置为CMS:

-XX:+UseConcMarkSweepGC

3

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

你在做什么?

处理的交易量是多少?

加载了多少数据?

等等。

也许您可以对应用程序进行分析并开始清理一些模块。

显然,这可能会在重新部署应用程序几次后发生

Tomcat具有热部署功能,但它会消耗内存。尝试定期重启容器。此外,您需要知道运行生产模式所需的内存量,这似乎是进行研究的好时机。


2

我唯一成功的方法是使用JRockit JVM。我有MyEclipse 8.6。

JVM的堆存储了运行Java程序生成的所有对象。Java使用new操作符创建对象,新对象的内存在运行时分配到堆中。垃圾回收是自动释放被程序不再引用的对象所包含的内存的机制。


2

如果您在eclipse IDE中设置了参数--launcher.XXMaxPermSize, -XX:MaxPermSize等,但仍然遇到相同的错误,那么很可能是eclipse正在使用由某些第三方应用程序安装并设置为默认的有缺陷的JRE版本。这些有缺陷的版本不会选择PermSize参数,因此无论您设置什么,仍然会出现内存错误。因此,请在您的eclipse.ini文件中添加以下参数:

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

同时,请确保您在Eclipse的首选项中设置了正确版本的Java作为默认JRE。


2
"他们"是错误的,因为我正在运行6.0.29版本,即使设置了所有选项,我仍然面临着同样的问题。正如Tim Howland在上面所说,这些选项只是推迟了不可避免的结果。它们只能让我重新部署三次才会出现错误,而不是每次重新部署都会出现错误。

2
我遇到了类似的问题。我的项目是基于JDK 7 + Maven 3.0.2 + Struts 2.0 + Google GUICE依赖注入的。
每当我尝试运行"mvn clean package"命令时,就会显示以下错误并出现"BUILD FAILURE":
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 我尝试了所有上述有用的技巧和窍门,但不幸的是都没有起作用。
对我有用的方法如下所述:
1. 进入pom.xml文件。 2. 搜索maven-surefire-plugin。 3. 添加一个新的元素,然后添加子元素,在其中传递"-Xmx512m -XX:MaxPermSize=256m",如下所示: -Xmx512m -XX:MaxPermSize=256m 希望这能有所帮助,祝你编程愉快 :)

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