Hibernate, SessionFactoryObjectFactory и OutOfMemoryError: пространство кучи Java

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

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

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

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

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

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

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

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

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

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

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

1 ответ

Решение

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

Виновником здесь было то, как загрузка пружинных бобов осуществлялась в различных объектах, особенно в классах 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;
    }
}

И вызывая это в классах webservice, вместо нового ClassPathXmlApplicationContext.

Обновить:

ClassPathXmlApplicationContext является Closeable/Autocloseable, так try-with-resourceеще одна возможность:

try (final ClassPathXmlApplicationContext applicationContext =
             new ClassPathXmlApplicationContext("applicationContext.xml")) {
    //do stuff
}
Другие вопросы по тегам