Содержание

четверг, 7 апреля 2011 г.

Permanent область памяти

Прежде всего замечу, что данная область не входит в часть кучи, выделяемой -Xmx. Для увеличения её объема необходимо использовать параметр -XX:MaxPermSize. Давайте посмотрим, что же хранится в данной области, и что может приводит к её переполнению.

Метаданные об объектах

С точки зрения java в этой области лежат те же объекты, что и в основной куче. Только это объекты определенных типов, а именно Class, Method, Field и Constructor. Я могу перечислить несколько причин роста числа таких объектов.
1. Библиотеки явно генерирующие байткод
Я знаю как минимум две библиотеки, способные генерировать байткод: acme и cglib. Так же начиная с java 6, теперь можно напрямую вызывать стандартный компилятор. В общем, раньше тоже можно было это сделать через Runtime.getRuntime().exec(). Если вы безудержно генерируете байткод и загружаете новые классы, то рано или поздно вы сталкнетесь с OutOfMemoryError.
2. Использование java.lang.reflect.Proxy
Данный стандартный JDK класс генерирует и подгружает новый Class. Называются такие классы как правило Proxy${N}, где N - целое число.
3. Библиотеки неявно генерирующие байткод
Hibernate, Spring, AspectJ и многие другие библиотеки могут генерировать классы или использовать dinamic proxy на лету.
4. Java Reflection API
Очевидно, что при использовании Reflection создаются объекты типа Field и Method, которые как уже упоминалось попадают в Permanent область. Но совсем не очевидно, что после многократного обращения к какому-нибудь полю класса через reflection или вызова его метода, генерируется и загружается новый класс. Дело в том, что в качестве оптимизации, для того чтобы в каждый раз не искать сдвиг в таблице, где лежат данные полей, JVM генерирует специальные классы, ускоряющие этот процесс. Называются они как правило GeneratedMethodAccessor{N}, GeneratedFieldAccessor{N}, GeneratedConstructorAccessor{N} где {N} - целое число. Конечно же такое поведение зависит от реализации JVM и никак не специфицируется, но на данный момент HotSpot JVM так делает.
5. Serialization
При использовании стандартной java сериализации используется reflection. С помощью него JVM считывает поле serialVersionUID, проверяет наличие метода writeObject, ищет все поля в классе, если writeObject не переопределен, считывает структуру родителя. Совет тут очевиден: не используйте интерфейс Serializable. Как минимум посмотрите решение Externalizable. Хотя я в последнее время предпочитаю Protocol Buffers, как более кросс-платформенное решение.
6. RMI
Как вы понимаете, RMI активно использует сериализацию, так что опять получаем reflection со всеми вытекающими отсюда эффектами.

Class data sharing (CDS)

В java 5 появилась такая вещь как CDS. Это файл на диске, являющийся частью инсталляции JVM, который содержит дапм JVM памяти, содержащий загруженные классы образующие ядро JVM, т.е. те которые наверняка JVM пришлось бы загрузить сразу при старте. Имею такую область, она сразу мапится при инициализации java машины, тем самым ускоряя время старта приложения. Как вы уже наверное догадались, данная область тоже попадает в Permanent область. По сути она содержит все те же объекты классов Class, только выгрузить их и очистить память JVM  уже не может.

String pool

Как вы наверняка знаете, в java есть пул строк. Все строчные литералы неявно кладутся в пул. Так же у класса java.lang.String есть метод intern(), который насильно кладет созданную любым способом строку в этот самый пул, который и располагается в нашей Permanent области. Еще я знаю, что имена методов и полей при использовании reflection попадают в пул. И еще говорят, что различные библиотеки по работе с XML, такие как xerces (на самом деле уже чать JDK) и JAXB тоже активно используют данный пул. Если у вас есть подозрение, что слишком много строк попадет в пул, то попробуйте поиграться с параметром -XX:PerfMaxStringConstLength. Я, правда, не смог найти документации на этот параметр, и, возможно, он делает что-то совсем другое. Если кто-нибудь пользовался данным параметром, то, пожалуйста, опишите свой опыт в комментариях.

Сборка мусора в PermGen

Когда говорят о сборщике мусора, то чаще всего данную область обозначают PermGen. По названию, казалось бы, что объекты созданные в этой области никогда не очищаются. Но не все и не всегда.
Например, пул строк никогда не очищается. Так же в случае CDS, Permanent область делится на две: только для чтения и для чтения-записи. Очевидно, что в этом случае область только для чтения не чиститься сборщиком мусора.
Чтобы диагностировать проблему загрузки большого количества классов и проверить выгружаются ли они или нет, можно использовать следующие параметры JVM
Чтобы включить/отключить выгрузку классов, можно воспользоваться параметрами
Если вы используете low pause сборщик мусора(CMS), то чтобы включить сборку в PermGen, нужно выставить два параметра (по крайней мере в java 5 надо было включить обязательно оба параметра, иначе не заработает)

6 комментариев:

  1. Подскажи, почему при редеплое в томкат, если его сделать несколько раз, кончается именно permgem?
    Есть ли способ очищать принудительно при передеплое?

    ОтветитьУдалить
  2. Скорее всего каждый деплой проходит в своем класслоадере. Возможно у вас не включен unload классов. Попробуйте опцию "-XX:+ClassUnloading" и после каждого деплоя делайте Full GC с помощью JConsole.

    Если у вас работает CMS, то должны помочь параметры -XX:+CMSPermGenSweepingEnabled -XX:+CMSClassUnloadingEnabled.

    Еще есть разные баги с библиотеками, которые возможно у вас используются. Посмотрите коменатрии к моему посту на хабре http://habrahabr.ru/blogs/java/117274/, там кто-то писал про такую же проблему и ему дали пару ссылок на возможные баги.

    ОтветитьУдалить
  3. Есть еще источник захламления пермгена :).

    В динамических языках типа Groovy, реализованных поверх JVM, есть поддержка для замыканий (лямбд) на уровне синтаксиса. Но поскольку JVM не поддерживает такие сущности, как замыкания, напрямую, каждое замыкание которое в коде может выглядеть очень коротко (например - myList.grep{it.date > today}, т.е. которое фильтрует список, выбирая из него элементы, удовлетворяющие предикату), на самом деле компилируется компилятором Groovy в отдельный Java .class, который загружается в JVM.

    Если у вас в системе много кода на таких динамеческих языках, и вы, идиоматически верно, активно используете замыкания, beware - замыкания тоже занимают место в PermGen.

    ОтветитьУдалить
    Ответы
    1. Не подскажите, как можно решить такую проблему, помимо как - не использования замыканий?

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

    ОтветитьУдалить
  5. спасибо очень интересная статья. попробую применить:)

    ОтветитьУдалить