Java memory leak

💡 Усі статті, обговорення, новини про Java — в одному місці. Приєднуйтесь до Java спільноти!

400 000 записів в таблиці.. в кожному рядку файлик по 3кб. треба їх витягнути з бази і записати в файлик. і це робити кожного дня.. (ппц:))
цей процес забирає дуже багато памяті і дані в памяті не очищаються після виконання цієї роботи.

private void exportTimeStampToFiles(File backupDir) throws IOException, ServiceException {
        File timestampDir = new File(backupDir, "timestamp");
        FileUtils.forceMkdir(timestampDir);
        int limit = 10000;
        for (int i = 0; ; i += limit) {
            Map<String, byte[]> timestampMap = this.exportDatabaseService.exportDocumentTimestamp(Integer.valueOf(limit), Integer.valueOf(i));
            for (Map.Entry<String, byte[]> entry : timestampMap.entrySet()) {
                byte[] value = entry.getValue();
                FileUtils.writeByteArrayToFile(new File(timestampDir, entry.getKey()), value);
            }
            int size = timestampMap.size();
            timestampMap = null;
            if (size < limit) {
                break;
            }

        }
    }
 public Map<String, byte[]> exportDocumentTimestamp(Integer limit, Integer offset) throws ServiceException {

        Map<String, byte[]> result = new FastMap<>();
        int iName = 0;
        int iTimeStamp = 1;

        SW.start();
        List<Object[]> objects = documentDao.exportTimestamp(limit, offset);
        logger.info("get timestamp from db = {}", SW.stop());
        for (Object[] doc : objects) {
            String name = (String) doc[iName];
            byte[] timestamp = (byte[]) doc[iTimeStamp];
            result.put(name, timestamp);
        }

        return result;
    }
public List<Object[]> exportTimestamp(Integer limit, Integer offset) {
        SQLQuery sqlQuery = SessionService.session()
                .createSQLQuery("SELECT name, time_stamp_response from document where time_stamp_response notnull  limit :limit offset :offset");
        sqlQuery.setParameter("limit", limit);
        sqlQuery.setParameter("offset", offset);
        sqlQuery.addScalar("name",StringType.INSTANCE);
        sqlQuery.addScalar("time_stamp_response", BinaryType.INSTANCE);
        return sqlQuery.list();
    }

що можете порадити?

Якщо коротко то алгоритм такий.. дістаю ці данні по 10000 рядків.. запихую в мапу. передаю мапу. ітерую мапу і записую валуе в файл..
використовую апачі лібу для запису в файл.. пробував без ліби.. теж саме.

i60.tinypic.com/2w3qohe.jpg
із скріна видно що гарбедж колектор все таки очищає непотрідні дані.. але виділена память не знижується

👍ПодобаєтьсяСподобалось0
До обраногоВ обраному0
LinkedIn
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

а отстреливать процесс и перезапускать эту мандулу по крону не подойдет? Это так что бы вовсе ни во что не вникать

Ідея з іншим процесом хороша але в мене не сильно виходить.. Тому що код для цієї мандали використовується ще в декількох місцях і винести в окрему джарку і запустити як процес в мене не виходить.. знаю можна запустити клас з мейн методом, як окремий процес, але щось це в мене не вийшло.

ну я бы все таки попробовал завернуть это как отдельную сущность. и тупо запускать только импорт по рассписанию. на остальное забить если разделить по людски не получается

Всім дякую за в-ді.. Критика вищезгаданого вітається)
использовать для выборки JDBC а не Hibernate
использовать масив, вместо Map
не писать в одну папку все 400000 файлов

для порций размером 10000, поставить
hibernate.jdbc.fetch_size=10000
new FastMap(10000)
если используется hibernate second level cache — добавить
sqlQuery.addSynchronizedQuerySpace("");

1. А насколько необходимо тянуть из базы всю последовательность данных сразу? Может стоит построчно читать курсором из базы? Да, по времени дольше, но Вы не будете одним скопом запихивать в память громоздкие объекты и GC будет успевать отрабатывать.
2. У вас задача: одно обращение — один файл? Или файл можно переиспользовать? Это я к тому, что, как говорит Капитан Очевидность, в файл еще и дописывать можно.
3. На кой Вам мапа? Разве нельзя как-то так?:


File timestampsDir = ... // get or create dir
for (Object[] doc : objects) {
String name = (String) doc[iName];
byte[] timestamp = (byte[]) doc[iTimeStamp];
File target = new File(timestampsDir, name);
target.createNewFile();
// write timestamp to file
FileUtils.writeByteArrayToFile(target, timestamp);
}
то есть писать сразу в файл при обращении к БД

Маленькое дополнение: если в проекте много работы с файлами — подключите Groovy — там с файлами работать в разы удобнее. Правда может и производительность упасть. Смотреть надо, короче.

Както не комифлю Груви подключать для еденичной работы с файлами.

для еденичной
Для единичной — да. Но может там такого много.

А простой command-line скрипт написать не пробовали? Такие вещи делать на Java — все равно, что из пушки по воробьям лупить

ну пушка, не пушка. а простота относительна. я вот сделаю такую задачу быстрее на scala/java, чем на bash/python.
И не потому, что scala/java лучший выбор для задачи.
Но потому, что python я практически не знаю, а на bash вообще не очевидно как к базе подключится... :)
И ради одной мелкой задачи срочно изучать другой язык нет смысла.

на bash ... к базе подключится...
Можно. Было дело, пробовал как-то. Но это:
1. Простыня линейного кода.
2. Куча регулярок.
3. Весьма сомнительный профит.
Лучше уж на scala/java/groovy.

У каждой базы есть родной команд-лайн клиент, который спокойно работает со стандартным входом и выходом. Разучился народ думать по-UNIX-овски. Я всегда делаю такие вещи простыми, быстрыми и легковесными средствами. Никогда нет проблем с памятью и производительностью.

Согласен. Профит есть. Регулярки — по-минимуму.
Только вот запускать sh-скриптец из Java-проекта как-то не очень... А если под винду перенесут? Тогда надо батник уже писать? Или Cygwin разворачивать?

Да что там command-line... Только asm, только хардкор :)

Может у него весь прект на Java. Сommand-line тут не очень подойдет. Лучше нативными вещами пользоваться.

Это да. Но всё равно проще jdbc драйвером без обёрток подключиться к базе и минимальным расходом той же памяти получить желаемое. Безо всяких утечек памяти. И отдельным приложением, запускаемым по кронтабу.

Задачу слежения за успешным прохождением процесса (контроль логов) можно легко передать другому сотруднику (админу).

було принято рішення винести це все в окремий процес + зменшити навантаження на память шляхом витягувати меньшими порціями. З записом в файл проблем немає окрім довгого виконання і роботи CPU.

Всім дякую за в-ді.. Критика вищезгаданого вітається)

в окремий процес
Окремий тред також підійшов би.
записом в файл проблем немає окрім довгого виконання і роботи CPU
Запапис у файл у Вас грузить проц? Ви що на дискету записуєте?
Щось тут не те...

Если я правильно помню LIMIT не ограничивает result set доставлять клиенту порционно, то есть все равно загружается весь result set от сервера а потом на него уже идет LIMIT. Когда решал проблему большого ответа от сервера то использовал jdbc resultset streaming.
Инфы по этому вопросу много, но начать можно к примеру отсюда:

neopatel.blogspot.com/...ming-large.html

Если я правильно помню LIMIT не ограничивает result set доставлять клиенту порционно, то есть все равно загружается весь result set от сервера а потом на него уже идет LIMIT.
LOL, неправильно помнишь.

И где именно там написано что при использовании ЛИМИТ резалт сет режется на клиенте а не на сервере?

Говносрачи — в личке

Ну и написал бы туда, чего тут разводишь?

«Туда» — это твоя ветка, мне там делать нечего

Туда — это в личку. Но тебе я вижу как раз интереснее срачи разводить.

Мой совет — напиши отдельное приложение, которое будет стартоваться старым добрым кронтабом, и если того желаешь — общаться с основным сервисом. После отработки тупо вышел из приложения и никаких хвостов в памяти.

Точно! И запускать это приложение на отдельной машине :)

Не исключено. Вопрос лишь в организации облака.

Ага. Для класса в 3 метода + драйвер БД покупем тазик на Амазоне :)
Opa, JavaEE-style!

Разрастается так потому что 10000 записей * 3 кб + объектные обертки вокруг это довольно много. Не знаю какие у вас настройки JVM, но так и до out of memory не далеко. Попробуйте записывать порциями в файл скажем по 100 строк, это увеличит обращения к диску, но уменьшит объем потребляемой памяти. Уменьшите мапу до 100 строк и делайте 100 записываний. Ну и поэкспериментируйте, может 20 записей по 500 строк.

Или используйте байтовый буффер с ограничением по объему, а когда достигнут предел, делайте flush, чтобы очистить его и начать заново.

Уменьшите мапу до 100 строк
Из того что можно увидеть в коде — там мапа вообще не нужна. Можно сразу после запроса в БД писать в файл.

Согласен, если просто запись в файл, то мапа не нужна, можно идти по Result-set-y набивать байтовый буфер или Reader и порциями писать в файл.

Я не знаю как по производітельности именно используемый тут драйвер, но если там хорошая оптимизация запросов, то можно по 10-20 строк выбирать курсором, а если запилить синхронизацию, то и в параллельных потоках (например 5 потоков по 20 строк). Проц нагрузит, но по памяти будет легче (зависит от настроек JVM отчасти).

із скріна видно що гарбедж колектор все таки очищає непотрідні дані.. але виділена память не знижується
JVM не отдает память назад в ОС, по крайней мере так было раньше. Если синенькое падает, то никакого меморилика нет.

Отдаёт. Но это зависит от настроек GC и ОС.
Вот хорошая статья на тему:
www.stefankrause.net/wp/?p=14

а ещё можно выделить эту задачу в отдельное приложение и запускать её по расписанию
(сделала грязную работу и завершилась). Тогда и бороться с GC не нужно будет.

Підписатись на коментарі