Утечка памяти MyBatis в запросе, результатом которого является список avg. 30 тыс. строк

В NetBeans Profiler я заметил, что Surviving Generations продолжает увеличиваться после выполнения запроса:

@Select("SELECT * FROM ais_dynamic WHERE rep_time >= #{from} AND rep_time <= #{to} AND ais_system = #{sys}")    
@Options(useCache=false,fetchSize=8192)
List<AisDynamic> getRecords(
        @Param("from") Timestamp from,
        @Param("to") Timestamp to,
        @Param("sys") int sys);

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

Ниже приведены текущие результаты, возвращаемые NetBeans Profiler: Текущие результаты NetBeans Profiler

Мои вопросы:

  1. Как я могу предотвратить утечку памяти?
  2. Как мне оптимизировать этот запрос, ведь я начал играться с Options, хотя это не предотвратило утечку памяти?

Если что-то нужно, пожалуйста, скажите, что и я предоставлю.

ОБНОВЛЕНИЕ:

После дополнительного тестирования я больше обеспокоен тем, что проблема заключается в том, что MyBatis содержит ссылку на полученные результаты, поэтому они не собираются мусором с течением времени. Выполнив 20 вызовов запроса, а затем ожидая, я не наблюдаю сборку мусора даже через 30 минут. Все, что я делаю, это вызываю метод: List<AisDynamic> adList = mapper.getRecords(from, to, sys);


person Boro    schedule 08.10.2012    source источник
comment
У меня также есть утечка памяти с MyBatis. Вы нашли решение своей проблемы?   -  person Marc    schedule 03.12.2012
comment
@Marc Я заметил, что это происходит только в ситуациях, когда я извлекаю большое количество строк, превышающих 10 тыс. Решение, которое сработало для меня, состояло в том, чтобы использовать JDBC и кодировать все вместе вручную, и проблема исчезла. Протестировано даже для 50 тыс. строк. Хотя это решило проблему для меня, я действительно предпочел бы полностью придерживаться MyBatis, поскольку это экономит много кода, но в этом случае это было невозможно. Хотя я жду лучшего решения, кто знает, может быть, нужно исправить API?   -  person Boro    schedule 05.12.2012
comment
Можете ли вы добавить эту конфигурацию в свой файл конфигурации MyBatis и посмотреть, есть ли разница: <settings><setting name="localCacheScope" value="STATEMENT"/></settings>   -  person partlov    schedule 08.04.2013


Ответы (1)


Я протестировал его на выходных, и похоже, что я решил проблему. Спасибо @partlov за предложение, хотя это не решение, оно заставило меня снова проверить проблему, и я обнаружил реальную проблему.

Проблема заключалась в том, что мой клиент, ответственный за обработку запросов запросов от пользователей, накапливал потоки (которые выполняли запросы). Поскольку запросы поступали очень часто, когда я проводил стресс-тестирование клиента, поэтому, когда предыдущий запрос не был выполнен, следующий запускался, хотя я отменял их, устанавливая и проверяя флаг в методе run() запроса. Это появлялось в ситуациях, когда сеанс запроса все еще разговаривал с базой данных, например. когда у выбора было 30 000+ результатов. Таким образом, хотя флаг отмены был поднят, он еще не был проверен, так как запрос находился в процессе извлечения результатов из базы данных. Этого времени было достаточно для запуска следующего запроса, поэтому, если он также имел много результатов, клиент накапливал потоки, фактически потребляя все больше и больше памяти.

Поскольку, похоже, нет способа (известного мне) отменить сеанс (например, запрос на выборку), который обращается к базе данных (в MyBatis), мне пришлось реализовать механизм защиты от него самостоятельно. Механизм, который я внедрил в свой клиент, гарантирует, что следующий запрос не будет запущен до тех пор, пока не завершится предыдущий (выполненный для того же пользователя). Таким образом, теперь запрос сообщает клиенту, когда он выходит из своего метода run(), и только тогда может быть запущен следующий запрос для того же пользователя.


Обновление Из опыта я узнал, что единственный и немного грязный способ (на мой вкус) прервать/отменить транзакцию с длительным получением — это вызвать метод close() экземпляра SqlSession, который использует транзакция. Это приведет к исключению (пример ниже), которое должно быть перехвачено и обработано должным образом.

org.apache.ibatis.exceptions.PersistenceException: 
### Error querying database.  Cause: java.lang.NullPointerException
### The error may exist in YourMapper.java (best guess)
### The error may involve methodOfTheHandlerInvolved
### The error occurred while handling results
### SQL: sqlOfYourQuery
### Cause: java.lang.NullPointerException
... (and a trace follows) ...
person Boro    schedule 25.04.2013