Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

Обзор утрат PermGen памяти в Java

Anna | 1.06.2014 | нет комментариев

О чем речь?

Кто занимался веб-разработкой на Java, наверно сталкивался с такой задачей какjava.lang.OutOfMemoryError: PermGen space. Появляется она, как правило, позже перезапуска веб-приложения внутри сервера без перезапуска самого сервера. Перезапуск веб-приложения без перезапуска сервера может потребоваться в процессе разработки, Дабы не ожидать лишнее время запуска самого сервера. Если у вас задеплоено несколько веб-приложений, перезапуск каждого сервера может быть значительно дольше перезапуска одного веб-приложения. Либо же каждый сервер легко невозможно перезапускать, так как другие веб-приложения применяются. Первое решение, которое приходит на ум – увеличить наивысший объем PermGen памяти, доступный JVM (сделать это дозволено опцией -XX:MaxPermSize), но это лишь отсрочит падение, позже нескольких перезапусков вы вновь получитеOutOfMemoryError. Отлично было бы иметь вероятность сколько желательно раз перезапускать и передеплоивать веб-приложение на работающем сервере. О том, как побороть PermGen, и пойдет последующий разговор.

Что такое PermGen?

PermGen – Permanent Generation – область памяти в JVM, предуготовленная для хранения изложения классов Java и некоторых дополнительных данных. Таким образом, при рестарте веб-приложения все классы загружаются по новой и заполняют PermGen память. Веб-приложение может содержать кучу библиотек, и изложения классов могут занимать десятки мегабайт. Кто следит за нововведениями в Java, может быть слышал о том, что в Java 8 отказались от PermGen. Здесь дозволено подумать, что нерушимую задачу, наконец, поправили, и огромнее не будет падений от недостатка PermGen памяти. К сожалению это не так, дерзко говоря, PermGen сейчас легко именуется Metaspace, и вы все равно получите OutOfMemoryError.

Стоп. А как же сборщик мусора?

Каждому нам вестимо, что в Java есть сборщик мусора, тот, что собирает все неиспользуемые объекты. Неиспользуемые классы в PermGen он тоже должен собирать, но только если он верно настроен, и отсутствуют утраты памяти.

Что касается настройки – официальной документации достаточно немного, в интернетах есть уйма советов применять разные опции, скажем -XX: CMSClassUnloadingEnabled-XX: CMSPermGenSweepingEnabled-XX: UseConcMarkSweepGC. Я не стал велико копать и искать официальную документацию, а способом проб и ошибок определил, что для Java 7 и Tomcat 7 нужно и довольно добавить JVM опцию -XX: UseConcMarkSweepGC. Эта опция изменит алгорифм сборки мусора, если вы не уверены, что ваше приложение не станет дрянней трудиться из-за этого, то поищите документацию и сопоставления работы различных алгорифмов сборки мусора, Дабы определить, стоит применять эту опцию либо нет. Допустимо, вам будет довольно включить эту опцию, Дабы избавиться от задач с PermGen. Если нет – то у вас, скорее каждого, утрата памяти, что с этим делать – читаем дальше.

Отчего происходит утрата PermGen памяти?

Для начала пара слов о class loader-ах. Class loader-ы – это объекты в Java, ответственные за загрузку классов. В веб-серверах существует иерархия class loader-ов, на всякое веб-приложение существует по одному class loader-у, плюс несколько всеобщих class loader-ов. Классы внутри веб-приложения загружаются class loader-ом, тот, что соответствует этому веб-приложению. Системные классы и классы, нужные самому серверу, загружаются всеобщими class loader-ами. Скажем, как устроена иерархия class loader-ов для Tomcat-а, дозволено почитать здесь.

Дабы сборщик мусора сумел собрать все классы веб-приложения, на них не должно быть ссылок вне этого веб-приложения. Сейчас припомним, что всякий объект в Java хранит ссылку на свой класс, т.е. на объект класса java.lang.Class, а всякий класс хранит ссылку на class loader, тот, что загрузил данный класс, а всякий class loader хранит ссылки на все классы, которые он загрузил. Получается, что каждого одна ссылка извне на объект веб-приложения тянет за собой все классы веб-приложения и неосуществимость собрать их сборщиком мусора.

Еще одной поводом утраты может быть поток, тот, что был запущен из веб-приложения, и тот, что не удалось остановить при остановке веб-приложения. Он также хранит ссылку на class loader веб-приложения.

Также знаменитым вариантом утраты является ThreadLocal переменная, которой присвоен объект из веб-приложения для потока из всеобщего пула. В этом случае поток хранит ссылку на объект. Поток из всеобщего пула не может быть уничтожен, значит объект не может быть уничтожен, значит и каждый class loader со всеми классами не может быть уничтожен.

Типовые средства Tomcat-а

К счастью в Tomcat-е существует целый ряд средств для обзора и предотвращения утрат PermGen памяти.

Во-первых, в стандартном Tomcat Manager Application есть кнопка «Find leaks» (подробности), которая проанализирует какие веб-приложения оставили позже себя мусор позже перезапуска.

Но это лишь покажет, какие веб-приложения допустимо содержат утрату, толку от этого немного.

Во-вторых, в Tomcat-е есть JreMemoryLeakPreventionListener — решение для общеизвестных допустимых вариантов утрат памяти, конфигурируется в server.xml (подробности). Допустимо, включение каких-либо опций этого listener-а поможет избавиться от утрат памяти.

И в-третьих самое основное – при остановке веб-приложения Tomcat пишет в лог что именно могло привести к утрате памяти. Скажем, так:

SEVERE: The web application [/drp] appears to have started a thread named [AWT-Windows] but has failed to stop it. This is very likely to create a memory leak.
SEVERE: The web application [/drp] created a ThreadLocal with key of type [org.apache.log4j.helpers.ThreadLocalMap] (value [org.apache.log4j.helpers.ThreadLocalMap@7dc1e95f]) and a value of type [java.util.Hashtable] (value [{session=*2CBFB7}]) but failed to remove it when the web application was stopped. Threads are going to be renewed over time to try and avoid a probable memory leak.

Вот это как раз то, что нам необходимо, Дабы продолжить обзор утрат.

И раз уж мы серьезно взялись за дело, нужно знать, как верно проверять очищается у нас PermGen либо нет. В этом нам вновь же поможет Tomcat Manager Application, тот, что может показывать применение памяти, в том числе PermGen.

Еще одна специфика – чистка происходит только позже достижения маскимального объема PermGen памяти, так что необходимо выставить малое значение максимальной доступной PermGen памяти (скажем, так: -XX:MaxPermSize=100M), Дабы позже 2-х-3 рестартов веб-приложения занятая память достигала 100%, и либо происходила чистка, либо падал OutOfMemoryError если утраты еще остались.

Сейчас разглядим, как избавиться от утрат на примерах

Возьмем следующее сообщение:

SEVERE: The web application [/drp] appears to have started a thread named [AWT-Windows] but has failed to stop it. This is very likely to create a memory leak.

Оно говорит нам о том, что веб-приложение запустило и не остановило поток AWT-Windows, следственно, у него contextClassLoader оказался class loader-ом веб-приложения, и сборщик мусора не может его собрать. Здесь мы можем отследить с поддержкой breakpoint-а с условием по имени потока, кто сотворил данный поток, и, покопавшись в исходниках, обнаружить, какие есть вероятности его остановить, скажем, проставить какой-то флаг либо вызвать какой-то способ, скажем Thread#interrupt(). Эти действия нужно будет исполнить при остановке веб-приложения.

Но еще дозволено подметить, что наименование потока схоже на что-то системное… МожетJreMemoryLeakPreventionListener, про тот, что мы узнали выше, что-то может сделать с этим потоком? Идем в документацию и видим, что подлинно у listener-а есть параметр AWTThreadProtection, тот, что отчего-то false по умолчанию. Проставляем его в true в server.xml и убеждаемся, что огромнее такого сообщения Tomcat не выдает.

В данном случае поток AWT-Windows создавался из-за генерации капчи на сервере с применением классов работы с изображениями из JDK.

Ок, здесь мы отделались примитивный опцией в Tomcat-е, испробуем что-нибудь потруднее:

SEVERE: The web application [/drp] created a ThreadLocal with key of type [org.apache.log4j.helpers.ThreadLocalMap] (value [org.apache.log4j.helpers.ThreadLocalMap@7dc1e95f]) and a value of type [java.util.Hashtable] (value [{session=*2CBFB7}]) but failed to remove it when the web application was stopped. Threads are going to be renewed over time to try and avoid a probable memory leak.

Здесь мы видим, что кто-то положил в ThreadLocal переменную класса ThreadLocalMap некоторое значение и не убрал его. Ищем, где применяется класс ThreadLocalMap, находим org.apache.log4j.MDC, а данный класс теснее непринужденно применяется в нашем веб-приложении для логгирования дополнительной информации. Видим, что вызывается способ put класса MDC, а способ remove не вызывается. Схоже, что вызов remove для всякого put в верном месте должен подмогнуть. Исправляем, проверяем – работает!

Позже исправления всех таких ошибок крупна вероятность, что вы избавитесь от OutOfMemoryError: PermGen space, по крайней мере, на моей практике это было так.

Обзор с поддержкой VisualVM

Если вы не используете Tomcat, либо если исправление ошибок указанных Tomcat-ом в логе не помогло, то дозволено продолжить обзор с поддержкой профайлера. Я взял безвозмездный профайлер VisualVM входящий в состав JDK.

Для начала запустим сервер с одним задеплоенным веб-приложением и перезапустим его, Дабы была видна утрата. Откроем VisualVM, предпочтем необходимый процесс и сделаем heap dump, предпочтя соответствующий пункт в выпадающем меню.

Предпочтем вкладку «OQL Console» и исполним такой запрос:
select x from org.apache.catalina.loader.WebappClassLoader x
(для других реализаций сервлета класс будет иным).

Один из 2-х экземпляров остался от первого остановленного веб-приложения, сборщик мусора не сумел его собрать. Дабы определить какой из них является ветхим – кликаем по одному из них и ищем поле started. У ветхого started будет false.

В окне «References» показываются все ссылки на данный class loader, нам необходимо обнаружить ту, из-за которой сборщик мусора не может его собрать. Для этого щелкаем правой кнопкой мыши по this и выбираем «Show Nearest GC Root».

чудесно, мы обнаружили какой-то поток, у которого наш ветхий class loader является contextClassloader-ом. Кликаем по нему правой кнопкой мыши и выбираем «Show Instance».

Глядим на поля объекта и думаем, за что мы можем зацепиться, Дабы осознать, что это за объект, как-то обнаружить код тот, что его создает, поймать в дебаггере, и т.п. В данном случае это имя потока – приятель нам AWT-Windows. Мы обнаружили ту же задачу, о которой писал нам Tomcat, только с поддержкой VisualVM. Как ее решить вы теснее знаете.

Вывод

Мы обучились определять, исследовать и исправлять утраты PermGen памяти. Оказалось это не так уж трудно, исключительно вследствие встроенным средствам Tomcat-а. Я не могу гарантировать, что приведенными выше методами дозволено избавиться от всех видов утрат, впрочем мне удалось таким образом избавиться от утрат в нескольких больших планах.

Ссылки

 

Источник: programmingmaster.ru

Оставить комментарий
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB