Содержание

четверг, 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 надо было включить обязательно оба параметра, иначе не заработает)