Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > Java tools & IDE's > JProfiler - нахождение "memory leak"


Автор: IDVsbruck 19.2.2007, 19:31
У меня достаточно простая программка - парсинг XML и занесение данных в БД.
Размер тестируемого XML - около 8 Мб, суммарно добавляется около 20-22 тыс. записей.
При работе памяти, стандартно выделенной JVR, не хватает. Добавлять параметры в строку запуска не хочу, да и не могу - проект будет раниться на разных машинах в разных местах и размер XML файла неизвестен - от сотен байт до десятков мегабайт, нет гарантии, что даже при большой выделенной памяти он рано или поздно не выдаст OutOfMemoryError.

Вручную перелопатил код взад и вперед - за один проход цикла добавляет суммарно от 10 до 40 Кб. За несколько сотен проходов съедает все.
Практически все вычисления вынес в вызываемые статические методы, но после возврата из них память уменьшается. И хотя это позволило завершить на моей машине работу, но это было как каз впритык ...

Скачал и установил JProfiler. Юзаю, юзаю smile ... но как с помощью него я могу отловить что-то, не очень пойму ... Может, кто даст дельный совет.

Спасибо.

Автор: y3u 19.2.2007, 20:48
Попробуй использовать какой-нибудь кеш. Небось все дерево в памяти держишь? smile В профайлере ты можешь посмотреть тенденции на предмет создания инстансов различных объектов. Очень вероятно, о чем писал в биттер джаве Тейт, что у тебя на строки очень много памяти уходит. Значит надо активнее пользоваться константами и не плодить кучи инстансов одинаковых объектов, хоть это и не совсем верно для строк, в виду наличия пула строк... Но, все-таки, я сильно сомневаюсь, что тебе в один момент времени нужно хранить в памяти все дерево, а значит можно, скажем, сериализовать какие-нибудь его куски на время, чтобы не висели в памяти, хотя, это, конечно, несколько скажется на скорости...

Автор: IDVsbruck 19.2.2007, 23:20
Возможно, что все дерево висит в памяти, но тогда все же направшивается вопрос - почему идет утечка памяти?
Путем проверки памяти на каждом этапе выполнения программы выяснил, что утечка как раз идет не при парсинге, а при записи в базу, что особенно странно.
Скажем, в main() вызываю свои методы, которые получают и обрабатывают данные из Документа, и выдают значения. Проверка показала, что память до и после получения равна. Далее вызыватся метод, который заносит данные в БД (упрощено):
Код

private static long setNames(String _f1, String _f2, String _f3)
{
    long result = -1;
    Statement statement = null;
    ResultSet resultSet = null;
    try {
        statement = connection.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE);
        resultSet = statement.executeQuery("SELECT * FROM names WHERE company='" + _f1 + "'");
        if (resultSet.next()) {
            result = resultSet.getLong("numid");
            resultSet.updateString("f2", _f2);
            resultSet.updateString("f3", _f3);
            resultSet.updateRow();
        } else {
            String SQLquery = "INSERT INTO names (company, f2, f3) VALUES ('" + _f1 + "', '" + _f2 + "', '" + _f3 + "')";
            int resultQuery = statement.executeUpdate(SQLquery);
            if (resultQuery != 0) {
                ResultSet lastLine = null;
                try {
                    lastLine = statement.executeQuery("SELECT TOP 1 * FROM names ORDER BY numid DESC");
                    lastLine.next();
                    result = lastLine.getLong("numid");
                } finally {
                    lastLine.close();
                }
            }
        }
        resultSet.close();
        statement.close();
    } catch (SQLException ex) {}
    return result;
}

Память съедается именно при обновлениях данных (resultSet.updateString(), resultSet.updateInt() и т.д.) - то есть при каждом апдейте доступная память уменьшается, какой остается при возврате из вызванного метода.
Переменные connection и resultSet пробовал делать как локальными, так и глобальными - результат идентичный ...

Автор: w1nd 19.2.2007, 23:26
Цитата(y3u @  19.2.2007,  20:48 Найти цитируемый пост)
Значит надо активнее пользоваться константами и не плодить кучи инстансов одинаковых объектов, хоть это и не совсем верно для строк, в виду наличия пула строк...

Именно так. Строки лучше при всяком удобном случае конструировать заново (даже явно).

Автор: IDVsbruck 20.2.2007, 01:38
Строки ... конструировать?
... инстансы ... где они?

Чу-у-уточку попдробнее, я, видимо, вас не сильно понимаю ....

Автор: y3u 20.2.2007, 11:30
Цитата(IDVsbruck @  19.2.2007,  23:20 Найти цитируемый пост)
Память съедается именно при обновлениях данных (resultSet.updateString(), resultSet.updateInt() и т.д.) - то есть при каждом апдейте доступная память уменьшается, какой остается при возврате из вызванного метода.
Переменные connection и resultSet пробовал делать как локальными, так и глобальными - результат идентичный ... 


ну, на ум сразу приходит мысль по поводу коннектора. Что за БД, что за коннектор? У меня была ситуация, когда некий коннектор под MSSQL срал нещадно во временную директорию винды - создавал там нереальное количество каких-то временных файлов, всвязи с чем у меня были проблемы и с памятью и с работой с БД - дело решилось заменой коннектора. 


Цитата(IDVsbruck @  20.2.2007,  01:38 Найти цитируемый пост)
Строки ... конструировать?
... инстансы ... где они?


ну это так, были общие мысли... 

ПыСЫ
кстати, для проверки наличия чего бы то ни было, лучше использовать запросы для этого предназначенные SELECT COUNT всякий там... А вместо  "INSERT INTO names (company, f2, f3) VALUES ('" + _f1 + "', '" + _f2 + "', '" + _f3 + "')"; нравственней бы было использовать PreparedStatement

Автор: IDVsbruck 20.2.2007, 15:48
Заремарил все обращения к БД (потери идут не всегда - раз в 3-4 обращения к базе (???) - видимо, действительно, драйвер барахлит), но утечка осталась практически на том же уровне.
Стал построчно проверять память. Это, конечно, дико, но единственное (!!!) место при прохождении по дереву XML, где теряется память - это выражение myNodeList.getLength(), вне зависимости от его расположения - в условиях цикла или при определении интовской переменной. Вот такие вот глюки в пакете org.w3c.dom - это уже стандартными средствами не исправишь!
Одно радует - есть возможность обходить поддерево не через NodeList и его метод getLength(), а через метод Node - getNextSibling() - итерациями.
Возможно, кто-то когда-то сталкивался с подобной "ерундой". Странно, что я нигде не встречал в инете даже упоминания о подобном глюке ...

З.Ы. Драйвер - sun.jdbc.odbc.JdbcOdbcDriver

Автор: y3u 20.2.2007, 18:28
а ты как, в for цикле небось обходил? скорее всего в этой имплементации - это вычисляемая функция, а ты, небось ее на каждой итерации в for дергал, попробуй либо написать for (int i = 0, j = myNodeList.getLength(); i < j; i++), либо, действительно, возьми другую-нибудь другую имплементацию заточенную для работы с большими XML-никами

ПыСы
А нафига тебе ODBC мост? С любой нормальной БД можно напрямую работать - уже коннекторов развелось полно. Или ты какой-нибудь Access или Excell используешь?

Автор: IDVsbruck 21.2.2007, 12:37
Вариант for (int i = 0, j = myNodeList.getLength(); i < j; i++) я привел - он так же "кушает", и вообще, любое обращение к getLength() НЕ возвращает память.
Видимо, действительно лучше подключить внешнюю библиотеку, работающую с большими XML-ми. Что наименее громоздко и наиболее удобно?

З.Ы.  smile  smile  smile  Ты совершенно прав - Access. Это не моя прихоть, мне как-то больше по душе PostreSQL, это база заказчика, которой пользуются множество его клиентов. Так что тут выбора, к сожалению, нет.

Автор: LSD 21.2.2007, 13:10
Цитата(IDVsbruck @  21.2.2007,  12:37 Найти цитируемый пост)
Видимо, действительно лучше подключить внешнюю библиотеку, работающую с большими XML-ми. Что наименее громоздко и наиболее удобно?

1. Ты не пробовал обновить JDK?
2. Попробуй Xerces, во всяком случае исходники у него есть и API совпадает с JAXP.

Автор: IDVsbruck 23.2.2007, 19:09
Хочу закрыть тему ...
Мои слабые знания на момент возникновения данной темы не позволили мне сразу разобраться в проблеме.

Действительно, дело в DOM парсере и как таковой "утечки памяти" не наблюдается. При парсинге моего 8-ми Мб-тового XML-файла DOM-парсер создал структуру, которая "легла" в памяти на 46 Мб. Так как первоначально в Document находится только структура, то по мере анализа ее и чтения данных они также помещаются в память. Выполнив ~80% работы, DOM-документ стал занимать в памяти ~64 Мб, после чего выдал злостную ошибку OutOfMemoryError. Видимо, это и есть доступная по умолчанию память для проекта, выделяемая JVM.

Единственным не совсем понятным для меня моментом стала такая ситуация: в private методе создаем Документ, парсим в него XML, закрываем поток, обнуляем Документ (null), возвращаемся в вызывающий метод ... а память остается на прежнем уровне. Но, по всей видимости, как об этом пишут источники, это не проблема с проектом, а "неспешная" работа Garbage Collector (Cleaner?) - он просто не успевает до конца выполнения проекта очистить память, так как на нее "пока никто не зарится".

Естественно, проблему решил с помощью технологии SAX - не так просто и наглядно как DOM, но очень шустро и с минимальными затратами памяти. Пик загрузки памяти у меня был ~845 Кб.

Большое спасибо за советы.

Автор: LSD 24.2.2007, 13:40
Цитата(IDVsbruck @  23.2.2007,  19:09 Найти цитируемый пост)
Единственным не совсем понятным для меня моментом стала такая ситуация: в private методе создаем Документ, парсим в него XML, закрываем поток, обнуляем Документ (null), возвращаемся в вызывающий метод ... а память остается на прежнем уровне. Но, по всей видимости, как об этом пишут источники, это не проблема с проектом, а "неспешная" работа Garbage Collector (Cleaner?) - он просто не успевает до конца выполнения проекта очистить память, так как на нее "пока никто не зарится".

GC будет вызываться по мере нобходимости, или его надо вызвать самому System.gc(). Но это не означает, что сразу вся память будет возвращена ОС.

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)