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
и строка, и эта структура повторяется несколько раз. SessionFactoryImpl
s содержит ряд объектов, в первую очередь 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
}