Hibernate, SessionFactoryObjectFactory и OutOfMemoryError: пространство кучи Java - PullRequest
7 голосов
/ 05 марта 2012

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

Сначала немного о рассматриваемой системе: этоJava-приложение, использующее Spring и Hibernate, для ведения записей об организациях.Система состоит из набора клиентов веб-сервисов, которые используются для получения данных об организациях из государственного учреждения, ответственного за этот тип данных.Кроме того, система хранит локальную базу данных с такими данными, выступая в качестве кэша для вызовов веб-службы, поэтому при первом запросе информации об организации она сохраняется в локальной реляционной базе данных и используется для поиска.данных для следующих запросов.Hibernate используется для связи с этой базой данных.

Проблема, как указывалось ранее, состоит в том, что через некоторое время приложение начинает аварийно завершать работу с OutOfMemoryError: пространство кучи Java.Я посмотрел на дамп кучи с помощью Eclipse + MAT и определил виновника как SessionFactoryObjectFactory Hibernate, занимающего приблизительно 85% выделенной памяти (все это сохраняло память).Мне было немного сложно определить, какие именно объекты хранятся в этом.На верхнем уровне находится Glassfish WebappClassLoader, который содержит org.hibernate.impl.SessionFactoryObjectFactory.В этом содержится org.hibernate.util.FastHashMap, который в свою очередь содержит java.util.HashMap.Он содержит несколько записей, каждая из которых содержит HashMap-запись, org.hibernate.impl.SessionFactoryImpl и строку.HashMap-запись, в свою очередь, содержит те же три объекта, HashMap-запись, SessionFactoryImpl и String, и эта структура повторяется несколько раз.SessionFactoryImpl s содержит ряд объектов, в частности org.hibernate.persister.entity.SingleTableEntityPersister, который содержит несколько Strings и HashMaps.Некоторые из Строк ссылаются на переменные в объектах домена, а некоторые содержат sql-операторы.

На первый взгляд казалось, что этот объект занимает ненужные объемы памяти (файл дампа был 800 МБ, из которых 650 МБбыл занят SessionFactoryObjectFactory), поэтому я включил ведение журнала загрузки и выгрузки объектов и попытался запросить у системы данные об организации (через вызов веб-службы из другой системы).Здесь я заметил, что было много сообщений о загрузке объектов, но очень мало о выгруженных объектах (единственными, которые были там, была выгрузка объектов библиотеки).Это привело меня к мысли, что, как только объект (скажем, организация) был загружен в память, он никогда не выгружается, что означает, что со временем в системе закончится память.(Это справедливое предположение, основанное на том, что было найдено в журнале?)

Затем я попытался найти причину этого, но это было намного сложнее.Поскольку объекты, загруженные Hibernate, будут жить так же долго, как и их сеансы, я попытался изменить способ обработки сеансов, заменив вызовы Spring HibernateDaoSupport#getSession() на HibernateDaoSupport#getSessionFactory().getCurrentSession().Это никак не повлияло на проблему.Я также попытался добавить вызовы к ... getCurrentSession().flush() и .clear() в блоке finally некоторых из рассматриваемых Dao-методов, также без видимого эффекта.(Все Dao-методы помечены @Transactional, что должно означать, что сеанс должен быть живым только в рамках @Transactional -метода, и последовательные вызовы метода должны получать разные сеансы при вызове getCurrentSession() (?))

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

Дамп кучи показал, что существует множество экземпляров org.hibernate.impl.SessionFactoryImpl, это как и ожидалось?(Я бы подумал, что должен быть только один экземпляр SessionFactory или несколько вершин.)

Редактировать:

Я думаю, что на самом деле мне удалось решить проблему:

Оказалось, что проблема, связанная с обработкой зависимостей от других объектов в классах webservice.Это было решено вызовом new ClassPathXmlApplicationContext(...) в конструкторе классов веб-сервиса.Это привело к тому, что для каждого запроса (или, по крайней мере, для каждого сеанса) загружалось много объектов, которые не выгружались снова (главным образом, SessionFactoryImpl в Hibernate).Я изменил webservice-классы, чтобы они вместо этого вводили свои зависимости, и сформировали то, что я видел с помощью профилировщиков, проблема с несколькими SessionFactoryImpl -объектами была решена.

Я думаю, что проблема могла усугубиться при обновлении с GlassFish 2.x до GlassFish 3.x, могут быть некоторые различия в том, как создаются экземпляры классов webservice.

1 Ответ

5 голосов
/ 07 декабря 2012

Я мог бы также добавить решение этой проблемы в ответ, а не просто в сам вопрос:

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

новый ClassPathXmlApplicationContext (...)

В отдельных веб-сервис-классах. Это приводит к неприятному побочному эффекту загружаемых объектов, избегая сбора мусора (я думаю, потому что на них ссылаются некоторые из внутренних компонентов Spring). Похоже, что изменение в версии Glassfish сделало что-то с созданием объектов webservice, что привело к вызовам гораздо большего количества вызовов new this и, таким образом, гораздо большего количества ненужных объектов, занимающих память, до тех пор, пока она не заполнится и не выйдет из строя.

Решением проблемы было перемещение звонков на

новый ClassPathXmlApplicationContext (...)

в другой класс, используя статический шаблон фабрики, что-то вроде этого:

public class ContextHolder {
    private static ClassPathXmlApplicationContext context;

    public static getSpringContext() {
        if (context == null) {
            context = new ClassPathXmlApplicationContext("applicationContext.xml");
        }
        return context;
    }
}

И вызов этого в классах веб-сервиса вместо нового ClassPathXmlApplicationContext.

Обновление:

ClassPathXmlApplicationContext - это Closeable / Autocloseable, поэтому try-with-resource - это еще одна возможность:

try (final ClassPathXmlApplicationContext applicationContext =
             new ClassPathXmlApplicationContext("applicationContext.xml")) {
    //do stuff
}
...