Это продолжение вчерашнего поста про сборщики мусора в JavaTM. Напоминаю, что все эту бодягу я начал с целью разобраться, что такое ошибка PermGenSpace: OutOfMemory exception.
Прежде чем рассказать о том, как работает сборка мусора в современной JVM (1.4, 1.5), давайте поговорим о поколениях объектов. В динамической памяти приложения некоторые объекты становятся мусором вскоре после создания, некоторые живут в течение долгого времени и только затем становятся мусором, другие могут остаться в живых в течение всего времени работы программы. Как показывает практика, в большинстве объектно-ориентированных языков, включая Java, огромное количество объектов, приблизительно 98 % , умирают молодыми. Возраст объектов можно измерять в секундах настенных часов, в общем количестве байтов, выделенных подсистемой управления памятью, с тех пор, как объект был размещен, или в числе сборок мусора с момента размещения объекта. Но как бы Вы не измеряли, большинство исследований показывает, что объекты умирают молодыми. Тут я даже постарался и с помощью Google Chart Creator нарисовал график:
Так как мы живем в неидеальном мире, у каждого из этих алгоритмов есть как преимущества, так и недостатки. Например, копирование хорошо работает, когда большой процент объектов является мусором (потому что этот вид сборщиков мусора не посещает мертвые объкты – он просто копирует живые объекты), но плохо работает с большим количеством долгоживущих объектов (периодически выполняя их копирование). Наоборот, маркировка-сжатие вполне хорошо работает с долгоживущими объектами (копируя их только один раз), но не так хорошо с большим количеством объектов, живущих недолго. Теперь, зная это, можно сделать вывод, что лучшим вариантом сборки мусора было бы использование копирующего сборщика мусора, а для долгоживущих – маркирующе-сжимающего. На основе подобных рассуждений, в Java был введен метод сборки мусора, берущий лучшее от обоих алгоритмов и нивелирующих их недостатки: так называемая «основанная на поколениях сборка мусора». Дополнительно он обеспечивает очень низкие затраты на размещение объектов.
Сборка мусора на основе поколений
Сборщик мусора, основанный на поколениях, делит динамическую память на несколько поколений – youth (молодые), tenured (Дословно – выжившие. На мой взгляд, очень поэтично :), perm – вечные. Youth дополнително делится на область генерации объектов Eden (совсем поэтично) и две полузоны для работы копирующего сборщика. Я еще не говорил. Объекты, отвечающие некоторым критериям продвижения, например, они пережили определенное число сборок мусора, затем переводятся в более старшее поколение – сначала Tenured (убирается маркирующе-сжимающим сборщиком мусора), затем и Perm (вообще не убирается). Одно из преимуществ сборки мусора на основе поколений заключается в том, что она может сократить паузы, не обрабатывая все поколения сразу. Если распределитель ресурсов не способен выполнить запросы на размещение, то он сначала запускает вспомогательные сборки мусора, которые обрабатывают только самое младшее поколение. Так как многие объекты в молодом поколении будут уже мертвы, а копирующему сборщику мусора совсем не надо проверять мертвые объекты, то паузы на вспомогательные сборки мусора могут быть достаточно малыми, и зачастую могут возвращать значительный объем динамической памяти. Если вспомогательная сборка мусора освобождает достаточно места в динамической памяти, то мутатор может немедленно возобновить работу. Если же она освобождает недостаточно места в динамической памяти, то сборка продолжится в более старших поколениях до тех пор, пока не будет возвращен достаточный объем памяти. (В случае если сборщик мусора не может возвратить достаточный объем памяти после полной сборки мусора, то он или расширит динамическую память, или выдаст OutOfMemoryError.) В принципе, уже из этого понятно, что такое PermGen, и почему у него заканчивается место при redeploy’е. Желающие узнать больше о принципах работы поколенного сборщика мусора могут обратиться к еще одной статье Брайна Гетца «Сборка мусора в HotSpot JVM«. Отдельно стоит упомянуть про многопроцессорные системы. В ранних версиях JDK сборка мусора в параллельных потоках сильно замедляла работу систему. Я даже спиздел из статьи Tuning Collection with the 5.0 Java[tm] Virtual Machine классную картинку, которая визуализирует зависимость между временем сборки мусора и количеством процессоров:
К счастью, в JDK 1.4 добавили специально для таких систем еще три сборщика мусора: параллельный копирующий сборщик мусора, параллельный утилизирующий сборщик мусора и синхронизированный маркирующе-чистящий сборщик. Эти новые сборщики призваны решить проблему сборки мусора, которая является основным припятствием для решения проблемы масштабируемости на многопроцессорных системах. Ну и напоследок еще одна пизженная у Брайна Гетца картинка, на которой проиллюстрированы рекомендации для выбора сборщика мусора:
Настройка JBOSS AS
PermGen Space : OutOfMemory exception вылетает от того, что оставшиеся от предыдущего deploy’я вечные объекты так и остаются в PermGen (это я не знаю, а предполагаю – и могу ошибаться) и PermGen Space рано или поздно переполняется. Соответственно, для решения этой проблемы нам надо как-то заставить сборщик мусора убирать более не нужные объекты в PermGen. На практике это выглядит так: 1. открываем файл run.conf в папке bin jboss’a, находим строку, начинающуюся с JAVA_OPTS и добавляем в нее следующие параметры: 2. Разрешаем сборщику мусора убирать PermGen: -XX:+CMSPermGenSweepingEnabled 3. Так как JVM так просто не отдаст на растерзание Perm объекты, надо умилостивить ее: -XX:+CMSClassUnloadingEnabled 4. Для того, чтобы сборщик мусора не прибил нужные нам объекты, нам нужно увеличить размер PermGen: -XX:MaxPermSize=128m 5. (Опционально) Если jboss вертится на многопроцессорной системе, то не забудьте включить синхронизированный сборщик мусора: -XX:+UseConcMarkSweepGC Для удобства использования выложу мою строку JAVA_OPTS:
JAVA_OPTS="-Xms256m -Xmx1024m -Dsun.rmi.dgc.client.gcInterval=3600000 -Dsun.rmi.dgc.server.gcInterval=3600000 -XX:+UseConcMarkSweepGC -XX:+CMSPermGenSweepingEnabled -XX:+CMSClassUnloadingEnabled -XX:MaxPermSize=128m"
Занимательно, жду следующего поста! :)
[...] куда-нибудь после того IF-a, что мы изменяли в предыдущей [...]
Интересная тема, Спасибо!
Картинки из поста куда-то потерялись. Поправьте пожалуйста
Да, спасибо. На выходных займусь.
Александр,
ваши действия окончательно решили проблему или всё же есть остаточные явления?