Работа с ошибкой "java.lang.OutOfMemoryError: PermGen space"

Недавно я столкнулся с этой ошибкой в ​​моем веб-приложении:

java.lang.OutOfMemoryError: пространство PermGen

Это типичное приложение Hibernate/JPA + IceFaces/JSF, работающее на Tomcat 6 и JDK 1.6. По-видимому, это может произойти после повторного развертывания приложения несколько раз.

Что вызывает это и что можно сделать, чтобы этого избежать? Как мне решить проблему?

34 ответа

Решение

Решением было добавить эти флаги в командную строку JVM при запуске Tomcat:

-XX:+CMSClassUnloadingEnabled -XX:+CMSPermGenSweepingEnabled

Это можно сделать, выключив службу tomcat, затем перейдя в каталог Tomcat/bin и запустив tomcat6w.exe. На вкладке "Java" добавьте аргументы в поле "Параметры Java". Нажмите "ОК" и перезапустите сервис.

Если вы получили ошибку, указанная служба не существует как установленная служба, вы должны запустить:

tomcat6w //ES//servicename

где servicename - это имя сервера, которое просматривается в services.msc

Источник: комментарий orx по поводу гибких ответов Эрика.

Ты лучше попробуй -XX:MaxPermSize=128M скорее, чем -XX:MaxPermGen=128M,

Я не могу сказать точное использование этого пула памяти, но это связано с количеством классов, загруженных в JVM. (Таким образом, разрешение выгрузки классов для tomcat может решить эту проблему.) Если ваши приложения генерируют и компилируют классы на ходу, более вероятно, что потребуется пул памяти больше, чем по умолчанию.

Ошибки PermGen сервера приложений, возникающие после нескольких развертываний, вероятнее всего вызваны ссылками, содержащимися в контейнере на загрузчики классов ваших старых приложений. Например, использование пользовательского класса уровня журнала приведет к тому, что ссылки будут храниться загрузчиком классов сервера приложений. Вы можете обнаружить эти утечки между классами-загрузчиками, используя современные (JDK6+) инструменты анализа JVM, такие как jmap и jhat, чтобы посмотреть, какие классы по-прежнему хранятся в вашем приложении, и изменить или исключить их использование. Обычно подозреваемыми являются базы данных, средства ведения журнала и другие библиотеки базового уровня.

См. Утечки Classloader: страшное исключение "java.lang.OutOfMemoryError: PermGen space", и особенно его последующий пост.

Распространенные ошибки, которые делают люди, - это думать, что куча пространства и пространство permgen одинаковы, что вовсе не так. У вас может быть много свободного места в куче, но все еще может не хватить памяти в permgen.

Распространенными причинами OutofMemory в PermGen является ClassLoader. Всякий раз, когда класс загружается в JVM, все его метаданные вместе с Classloader хранятся в области PermGen, и они будут собираться мусором, когда загрузчик Classloader, который их загрузил, готов для сбора мусора. В случае, если Classloader имеет утечку памяти, все загруженные им классы останутся в памяти и вызовут излишнюю память permGen, если вы повторите это пару раз. Классическим примером является Java.lang.OutOfMemoryError:PermGen Space в Tomcat.

Теперь есть два способа решить это:
1. Найдите причину утечки памяти или ее утечки.
2. Увеличьте размер пространства PermGen с помощью параметра JVM -XX:MaxPermSize а также -XX:PermSize,

Вы также можете проверить 2 Solution of Java.lang.OutOfMemoryError в Java для более подробной информации.

Используйте параметр командной строки -XX:MaxPermSize=128m для Sun JVM (очевидно, заменяя 128 на любой нужный вам размер).

Пытаться -XX:MaxPermSize=256m и если оно не исчезнет, ​​попробуйте -XX:MaxPermSize=512m

Я добавил -XX: MaxPermSize = 128m (вы можете поэкспериментировать, который работает лучше всего) с VM Arguments, так как я использую Eclipse Ide. В большинстве JVM значение PermSize по умолчанию составляет около 64 МБ, что приводит к нехватке памяти, если в проекте слишком много классов или огромное количество строк.

Для затмения это также описано в ответе.

ШАГ 1: Двойной щелчок на сервере Tomcat на вкладке Servers

ШАГ 2: Откройте запуск Conf и добавьте -XX: MaxPermSize = 128m до конца существующих аргументов VM.

Я бился головой об этой проблеме, когда развертывал и развертывал сложные веб-приложения, и думал, что добавлю объяснение и свое решение.

Когда я развертываю приложение в Apache Tomcat, для него создается новый ClassLoader. ClassLoader затем используется для загрузки всех классов приложения, а при отмене развертывания все должно пройти хорошо. Однако на самом деле все не так просто.

Один или несколько классов, созданных в течение жизни веб-приложения, содержат статическую ссылку, которая где-то вдоль линии ссылается на ClassLoader. Поскольку ссылка изначально статична, никакие сборщики мусора не очистят эту ссылку - ClassLoader и все загруженные классы останутся здесь.

И после нескольких повторных развертываний мы сталкиваемся с ошибкой OutOfMemoryError.

Теперь это стало довольно серьезной проблемой. Я мог бы убедиться в том, что Tomcat перезапускается после каждого повторного развертывания, но это приводит к удалению всего сервера, а не только повторного развертывания приложения, что часто неосуществимо.

Поэтому вместо этого я собрал решение в коде, которое работает на Apache Tomcat 6.0. Я не тестировал ни на каких других серверах приложений и должен подчеркнуть, что, скорее всего, это не сработает без изменений на любом другом сервере приложений.

Я также хотел бы сказать, что лично я ненавижу этот код, и что никто не должен использовать его как "быстрое исправление", если существующий код может быть изменен для использования надлежащих методов выключения и очистки. Единственный раз, когда это следует использовать, это если существует внешняя библиотека, от которой зависит ваш код (в моем случае это был клиент RADIUS), который не предоставляет средства для очистки своих собственных статических ссылок.

Во всяком случае, с кодом. Это следует вызывать в тот момент, когда приложение развертывается - например, метод уничтожения сервлета или (лучший подход) метод contextDestroyed ServletContextListener.

//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 разделена на различные области, которые можно увидеть на следующем рисунке:

Metaspace: рождается новое пространство памяти

JVK HotSpot JDK 8 теперь использует собственную память для представления метаданных класса и называется Metaspace; похож на 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

1) Увеличение объема памяти PermGen

Первое, что можно сделать, - это увеличить размер кучи постоянного поколения. Это нельзя сделать с помощью обычных аргументов JVM –Xms (установить начальный размер кучи) и –Xmx (установить максимальный размер кучи), поскольку, как уже упоминалось, пространство кучи постоянного поколения полностью отделено от обычного пространства кучи Java, и эти параметры устанавливаются пространство для этого обычного пространства кучи Java. Однако есть аналогичные аргументы, которые можно использовать (по крайней мере для jvms Sun/OpenJDK), чтобы увеличить размер кучи постоянного поколения:

 -XX:MaxPermSize=128m

По умолчанию это 64м.

2) Включить Sweeping

Еще один способ позаботиться об этом навсегда - разрешить выгружать классы, чтобы ваш PermGen никогда не заканчивался:

-XX:+CMSClassUnloadingEnabled -XX:+CMSPermGenSweepingEnabled

Подобные вещи работали для меня волшебством в прошлом. Одна вещь, тем не менее, есть существенный компромисс производительности при их использовании, так как развертки permgen будут делать как дополнительные 2 запроса на каждый запрос, который вы делаете, или что-то в этом роде. Вам нужно будет сбалансировать свое использование с компромиссами.

Вы можете найти детали этой ошибки.

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

Самый простой ответ в наши дни - использовать Java 8.

Он больше не резервирует память исключительно для пространства PermGen, позволяя памяти PermGen смешиваться с обычным пулом памяти.

Имейте в виду, что вам придется удалить все нестандартные -XXPermGen...=... Параметры запуска JVM, если вы не хотите, чтобы Java 8 жаловалась, что они ничего не делают.

У меня была проблема, о которой мы здесь говорим, мой сценарий - eclipse-helios + tomcat + jsf, и вы делали развертывание простого приложения для tomcat. Я показывал здесь ту же проблему, решил ее следующим образом.

В eclipse перейдите на вкладку серверов двойным щелчком мыши по зарегистрированному серверу в моем случае tomcat 7.0, на моем файловом сервере откроется общая информация о регистрации. В разделе "Общая информация" перейдите по ссылке "Открыть конфигурацию запуска", это откроет выполнение параметров сервера на вкладке "Аргументы" в аргументах виртуальной машины, добавленных в конце этих двух записей.

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

и готово.

  1. Откройте tomcat7w из каталога bin Tomcat или введите Monitor Tomcat в меню "Пуск" (откроется окно с вкладками с различной служебной информацией).
  2. В текстовой области Параметры Java добавьте эту строку:

    -XX:MaxPermSize=128m
    
  3. Установите начальный пул памяти на 1024 (необязательно).
  4. Установите Максимальный пул памяти на 1024 (необязательно).
  5. Нажмите Ok.
  6. Перезапустите сервис Tomcat.

Ошибка пространства Пермского пространства возникает из-за использования большого пространства, а не пространства, предоставленного jvm для выполнения кода. Лучшее решение этой проблемы в операционных системах UNIX - изменить некоторые настройки в файле bash. Следующие шаги решают проблему.

Команда запуска gedit .bashrc на терминале.

Создайте JAVA_OTPS переменная со следующим значением:

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

Сохраните файл bash. Запустите команду exec bash на терминале. Перезагрузите сервер.

Я надеюсь, что этот подход будет работать на вашу проблему. Если вы используете версию Java ниже 8, эта проблема иногда возникает. Но если вы используете Java 8, проблема никогда не возникает.

Увеличение размера постоянной генерации или настройка параметров GC НЕ поможет, если у вас есть реальная утечка памяти. Если ваше приложение или какая-либо сторонняя библиотека, которую оно использует, пропускает загрузчики классов, единственное реальное и постоянное решение - найти эту утечку и исправить ее. Есть ряд инструментов, которые могут вам помочь, одним из последних является Plumbr, который только что выпустил новую версию с необходимыми возможностями.

Также, если вы используете log4j в своем веб-приложении, проверьте этот параграф в документации log4j.

Кажется, что если вы используете PropertyConfigurator.configureAndWatch("log4j.properties"), вы вызываете утечки памяти при отмене развертывания вашего веб-приложения.

Я попробовал несколько ответов, и единственной вещью, которая наконец сделала работу, была эта конфигурация для плагина компилятора в pom:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>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 -->
        <!-- <argLine>-Xmx512m -XX:MaxPermSize=512m</argLine> -->
    </configuration>
</plugin>

надеюсь, это поможет.

jrockit решил и это для меня; Тем не менее, я заметил, что время перезапуска сервлета было намного хуже, поэтому, хотя он был лучше в производстве, это было своего рода тормозом в развитии.

Задавать -XX:PermSize=64m -XX:MaxPermSize=128m, Позже вы также можете попробовать увеличить MaxPermSize, Надеюсь, это сработает. То же самое работает для меня. Настройка только MaxPermSize не работал для меня.

У меня есть комбинация Hibernate+Eclipse RCP, пробовал использовать -XX:MaxPermSize=512m а также -XX:PermSize=512m и это, кажется, работает для меня.

Они говорят, что последняя версия Tomcat (6.0.28 или 6.0.29) намного лучше справляется с задачей повторного развертывания сервлетов.

Первый шаг в таком случае - проверить, разрешено ли GC выгружать классы из PermGen. Стандартная JVM довольно консервативна в этом отношении - классы рождены, чтобы жить вечно. Поэтому после загрузки классы остаются в памяти, даже если их больше не использует ни один код. Это может стать проблемой, когда приложение динамически создает много классов, и сгенерированные классы не нужны в течение более длительных периодов времени. В таком случае может помочь JVM выгрузить определения классов. Это может быть достигнуто путем добавления только одного параметра конфигурации в ваши сценарии запуска:

-XX:+CMSClassUnloadingEnabled

По умолчанию для этого параметра установлено значение false, поэтому для его включения необходимо явно указать следующую опцию в параметрах Java. Если вы включите CMSClassUnloadingEnabled, GC также будет сканировать PermGen и удалять классы, которые больше не используются. Имейте в виду, что этот параметр будет работать только в том случае, если UseConcMarkSweepGC также включен с использованием указанного ниже параметра. Поэтому при запуске ParallelGC или, не дай Бог, Serial GC, убедитесь, что вы установили GC на CMS, указав:

-XX:+UseConcMarkSweepGC

Назначение 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>  

Конфигурация памяти зависит от характера вашего приложения.

Что делаешь?

Какое количество транзакций обработано?

Сколько данных вы загружаете?

и т.п.

и т.п.

так далее

Возможно, вы могли бы профилировать свое приложение и начать очистку некоторых модулей из вашего приложения.

Видимо, это может произойти после повторного развертывания приложения несколько раз

Tomcat имеет горячее развертывание, но он потребляет память. Попробуйте перезапустить ваш контейнер время от времени. Кроме того, вам необходимо знать объем памяти, необходимый для работы в производственном режиме, это, кажется, хорошее время для этого исследования.

Я столкнулся с точно такой же проблемой, но, к сожалению, ни одно из предложенных решений не помогло мне. Проблема не возникла во время развертывания, и я не делал ни одного горячего развертывания.

В моем случае проблема возникала каждый раз в один и тот же момент во время выполнения моего веб-приложения при подключении (через спящий режим) к базе данных.

Эта ссылка (также упоминавшаяся ранее) предоставила достаточно внутренностей для решения проблемы. Перемещение jdbc-(mysql)-драйвера из WEB-INF в папку jre/lib/ext/, похоже, решило проблему. Это не идеальное решение, так как обновление до новой JRE потребует переустановки драйвера. Другим кандидатом, который может вызвать аналогичные проблемы, является log4j, так что вы можете захотеть переместить и этот.

У меня была похожая проблема. Мой 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; Вложенное исключение - java.lang.reflect.InvocationTargetException: null java.lang.reflect.InvocationTargetException. Причина: java.lang.OutOfMemoryError: пространство PermGen.

Я испробовал все вышеперечисленные полезные советы и рекомендации, но, к сожалению, ни один из них не помог мне. То, что сработало для меня, описано шаг за шагом ниже:=>

  1. Перейти к вашему pom.xml
  2. Ищи <artifactId>maven-surefire-plugin</artifactId>
  3. Добавить новый <configuration> элемент, а затем <argLine> подэлемент, в котором проходят -Xmx512m -XX:MaxPermSize=256m как показано ниже =>

<configuration> <argLine>-Xmx512m -XX:MaxPermSize=256m</argLine> </configuration>

Надеюсь, это поможет, счастливого программирования:)

Если вы получаете это в Eclipse IDE, даже после установки параметров --launcher.XXMaxPermSize, -XX:MaxPermSizeи т. д., тем не менее, если вы получаете ту же ошибку, скорее всего, затмение использует ошибочную версию JRE, которая была бы установлена ​​некоторыми сторонними приложениями и установлена ​​по умолчанию. Эти версии с ошибками не принимают параметры PermSize, и поэтому независимо от того, что вы установили, вы все равно будете получать эти ошибки памяти. Итак, в ваш eclipse.ini добавьте следующие параметры:

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

Также убедитесь, что вы установили JRE по умолчанию в настройках затмения для правильной версии Java.

"Они" не правы, потому что я работаю 6.0.29 и у меня та же проблема, даже после установки всех параметров. Как сказал Тим Хоулэнд выше, эти варианты только откладывают неизбежное. Они позволяют мне выполнить повторное развертывание 3 раза, прежде чем совершать ошибку, а не каждый раз, когда я повторяю развертывание

Единственный способ, который работал для меня, был с JRockit JVM. У меня есть MyEclipse 8.6.

В куче JVM хранятся все объекты, созданные с помощью работающей Java-программы. Java использует new оператор для создания объектов, а память для новых объектов выделяется в куче во время выполнения. Сборка мусора - это механизм автоматического освобождения памяти, содержащейся в объектах, на которые программа больше не ссылается.

Другие вопросы по тегам