Quartz + Hibernate: простая утилита пожирает память.Что может быть не так? - PullRequest
2 голосов
/ 16 ноября 2011

* РЕДАКТИРОВАТЬ: Я сам нашел ответ на утечку памяти, и отправил его вместе с другими.Если кто-то может ответить, почему я должен применить исправление вообще (см. Вопрос в моем ответе), я с радостью приму ответ :-) *

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

Задание было настроено с использованием следующего триггера cron «* * / 2 * * *?», Что означает «запускать каждыйпару минут".Странно то, что после того, как первая работа была закончена (на это ушло около часа), оставшиеся рабочие места быстро сработали, одна за другой.Это было неожиданно, так как я думал, что между каждой работой должно быть две минуты.

Теперь мне интересно, что может быть не так.Я предполагаю, что причина в том, как я использую Hibernate и / или Quartz.Может быть, я не выпускаю сеанс так, как должен, или неправильно понял, как Quartz планирует задания? Или, может быть, я должен внедрить фабрику Hibernate в работу, а не создавать ее в каждой работе?getCurrentSession () против openSession ()?Без понятия.Я до сих пор не понимаю, почему это должно иметь какой-либо эффект, так как задания должны освободить все ресурсы при выходе.Во всяком случае, большая часть соответствующего кода будет показана ниже.

Независимо от того, решена ли проблема с подключением, код страдает от некоторой утечки памяти.Как будто каждая выполненная работа каким-то образом хранится в куче.Выполнив

java -Xmx17M -DcronTriggerExpression="*/1 * * * * ?" -jar myjar.jar,

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

public class ExportJob implements Job {
    LogImporter importer;

    public ExportJob() { //needed for Quartz 
        setUpHibernate(); 
        importer = new LogImporter(); //injected, but this saves space on SO :)
    }

    public void execute(JobExecutionContext context) throws JobExecutionException {
        /* even if this body is empty, I get a OutOfMemoryError */        
    }

    private SessionFactory setUpHibernate() {
        logger.debug("Setting up hibernate");
        this.sessionFactory = new Configuration().configure().buildSessionFactory();
        return this.sessionFactory;
    }

    private Session getSession() {
        return sessionFactory.openSession();
    }
}

Ответы [ 4 ]

2 голосов
/ 16 ноября 2011

По крайней мере, быстрый огонь вызван кварцем. Он ставит в очередь задания каждые 2 минуты, поэтому через час у вас должно начаться 59 заданий.

Вы правы, закрывая SessionFactory, а не сеанс решает проблему. Это также объясняет запуск нашего соединения, просто чтобы замкнуть петлю - и, надеюсь, получить награду ;-). Хотя Hibernate допускает несколько SessionFactory, каждый из них будет поддерживать соединение.

Из их документации:

Когда все отображения были проанализированы org.hibernate.cfg.Configuration, приложение должно получить фабрику для экземпляров org.hibernate.Session. Этот завод предназначен для общий для всех потоков приложений. Hibernate позволяет приложение для создания более одного org.hibernate.SessionFactory. Это полезно, если вы используете более одной базы данных.

Таким образом, окончательное исправление должно состоять в том, чтобы создать один SessionFactory в другом месте и повторно использовать его для генерации сеансов в каждом потоке или в этом случае задания.

1 голос
/ 16 ноября 2011

Оказывается, мне пришлось закрыть SessionFactory после использования. Если бы я этого не сделал, сессионные фабрики по какой-то причине остались бы в памяти. Так что просто добавив sessionFactory.close() в конце execute(), получилось. Нет больше OutOfMemoryError.

Теперь почему Я должен сделать это за меня: я думал, что обычные правила Java все еще применяются. Когда задание больше не выполняется (Quartz, вероятно, обнуляет его), все зависимые объекты подвергаются сборке мусора (включая SessionFactory). Это должно было освободить его ресурсы. Или я так подумал?

1 голос
/ 16 ноября 2011

Из вашей первой версии поста я заметил следующий код:

 for (LogEntry logEntry : newEntries) {
        session.save(logEntry);
        ...................
}

Каждый раз, когда вы сохраняете новый logEntry, он сохраняется в сеансе гибернации.Каждый постоянный объект помещается в кэш первого уровня (память вашей JVM) и не будет CG.Таким образом, если вы сохраните много logEntry в транзакции, вы можете столкнуться с исчерпанием памяти.Вы должны очистить и очистить сеанс гибернации, чтобы освободить часть памяти после сохранения определенного количества объектов. Для лучшей практики пакетной вставки используйте this , используя hibernate

Кстати, вы устанавливаете максимальный размер кучитолько до 17 МБ, что настолько мало, что ваше приложение может легко исчерпать память до того, как будет запущен настоящий проблемный код. Так, OutOfMemoryError может из-за кодов внутри цикла while.

Я предлагаю вамВозьмите дамп кучи при возникновении OutOfMemoryError. Затем проверьте дамп кучи, чтобы увидеть, какие экземпляры могут вызвать утечку памяти.Вы можете сослаться this о том, как использовать VisualVM для сбора и анализа дампа кучи.

1 голос
/ 16 ноября 2011

Не уверен, поможет ли это, но похоже, что вы хотите, чтобы задание запускалось последовательно каждые 2 минуты. Если это так, вы можете реализовать StatefulJob вместо Job. Таким образом, вы можете быть уверены, что выполняющиеся параллельные задания не будут связывать все соединения с базой данных.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...