Там, где я работаю, у нас возникают проблемы с нехваткой пространства кучи на 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.