Найти в Дзене
Антон Рязанцев

Java. Поиск и устранение одной утечки памяти

Оглавление

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

Что мы имеем

Есть проект, который я первый раз вижу, система учета проезда машин через КПП. Стек средств и технологий: Tomcat 8, Java 1.8, GWT 2.5, Hibernate 3.6.7, Maven. При определенных настройках фильтрации пунктов пропуска использование памяти растет до потолка и портал перестает работать.

С чего начать

Почему то вначале заказчик намекал, что дело в устаревшем Tomcat. Пробовал обновлять Tomcat, Hibernate, но нет, это путь на удачу и скорее бесполезный. Правильно начинать с диагностики и анализа, и хорошо, что средства для этого есть. Самое простое — это Java VisualVM, которое идет в комплекте с Java.

Java VisualVM

Запускаем, проделываем махинации в ПО, после которых начинает расти память. Результат на рисунке:

Память быстро заполняется
Память быстро заполняется
Что там в куче
Что там в куче

Видно, что память занимают строки и много занимают объекты Hibernate, что наводит на мысль на взаимосвязь. Можно сделать snapshot и посмотреть классы, но делать это в Java VisualVM очень не удобно, он тормозит, плохо структурирует. Поискав удобное средство для анализа дампа памяти, выбор пал на Mat (Eclipse Memory Analyzer), потому что бесплатный.

Mat (Eclipse Memory Analyzer)

В Mat необходимо открыть дамп памяти далее перейти к Dominator Tree.

Открываем Dominator Tree
Открываем Dominator Tree
Потоки заняли память
Потоки заняли память
В каждом потоке зависший запрос в БД, размером в 16 Мб
В каждом потоке зависший запрос в БД, размером в 16 Мб

Dominator Tree отображает содержимое дампа памяти в виде дерева java-объектов, сразу упорядочивает от большего к меньшему. Видим, что память заняли несколько одинаковых по размеру потоков. Раскрыв в дереве содержимое одного из потоков видна вся суть проблемы. Мы видим SQL-запрос размером 16 Мб. Получается где-то неправильно формируется запрос с помощью Hibernate, остается найти это место и исправить. Проанализировав текст запроса, найти по коду место его формирования не составляет труда.