Java - Избежание повторяющегося ручного сбора мусора - mstor и javaxmail OutOfMemoryError

Я использую библиотеку mstor для анализа почтового файла mbox. Размер некоторых файлов превышает гигабайт. Как вы можете себе представить, это может вызвать проблемы с кучей пространства.

Есть цикл, который для каждой итерации извлекает конкретное сообщение. getMessage() вызов - это то, что пытается выделить пространство кучи, когда оно заканчивается. Если я добавлю звонок в System.gc() В верхней части этого цикла программа анализирует большие файлы без ошибок, но я понимаю, что сбор мусора 40000 раз должен замедлять работу программы.

Моей первой попыткой было сделать вызов похожим if (i % 500 == 0) System.gc() звонить каждые 500 записей. Я пытался поднять и опустить это число, но результаты противоречивы и обычно возвращают ошибку OutOfMemory.

Моя вторая, более умная попытка выглядит так:

try {
    message = inbox.getMessage(i);
} catch (OutOfMemoryError e) {
    if (firstTry) {
        i--;
        firstTry = false;
    } else {
        firstTry = true;
        System.out.println("Message " + i + " skipped.");
    }
    System.gc();
    continue;
}

Идея состоит в том, чтобы вызывать сборщик мусора только в случае возникновения ошибки OutOfMemory, а затем уменьшать счетчик, чтобы повторить попытку. К сожалению, после анализа нескольких тысяч электронных писем программа просто начинает выводить:

 Message 7030 skipped.
 Message 7031 skipped.
 ....

и так далее для всех остальных.

Я просто запутался в том, как попадание в коллектор для каждой итерации будет давать разные результаты, чем это. Насколько я понимаю, мусор - это мусор, и все, что должно измениться, - это сколько собирать в данный момент времени.

Кто-нибудь может объяснить это странное поведение? У кого-нибудь есть рекомендации по другим способам вызова коллектора реже? Мое пространство кучи исчерпано.

5 ответов

Решение

Библиотека mstor плохо справлялась с кэшированием сообщений. После некоторых исследований я обнаружил, что если вы позвоните Folder.close() (Входящие - это объект моей папки выше) mstor и javaxmail освобождают все сообщения, которые были кэшированы в результате getMessage() метод.

Я сделал блок try / catch похожим на это:

try {
    message = inbox.getMessage(i);
    // moved all of my calls to message.getFrom(),
    // message.getAllRecipients(), etc. inside this try/catch.
} catch (OutOfMemoryError e) {
    if (firstTry) {
        i--;
        firstTry = false;
    } else {
        firstTry = true;
        System.out.println("Message " + i + " skipped.");
    }
    inbox.close(false);
    System.gc();
    inbox.open(Folder.READ_ONLY);
    continue;
}
firstTry = true;

При каждом обращении к оператору catch требуется 40-50 мс для ручной очистки кэшированных сообщений и повторного открытия папки.

При вызове сборщика мусора через каждую итерацию для анализа файла размером 1,6 ГБ потребовалось 57 минут. При такой логике для анализа одного и того же файла требуется всего 18 минут.

Обновление. Еще один важный аспект уменьшения объема памяти, используемой mstor, связан со свойствами кэша. Кто-то уже упоминал, что для параметра "mstor.cache.disabled" установлено значение true, и это помогло. Сегодня я обнаружил еще одно важное свойство, которое значительно уменьшило количество уловов OOM для еще больших файлов.

    Properties props = new Properties();
    props.setProperty("mstor.mbox.metadataStrategy", "none");
    props.setProperty("mstor.cache.disabled", "true");
    props.setProperty("mstor.mbox.cacheBuffers", "false");   // most important

Вот мои предложения:

  • Увеличьте кучу места. Это, наверное, самая простая вещь, которую нужно сделать. Вы можете сделать это с -Xmx, параметр.
  • Посмотрите, предоставляет ли API для загрузки сообщений функцию потоковой передачи. Возможно, вам не нужно загружать все сообщение в память сразу.

призвание System.gc() не принесет вам пользы, потому что это не гарантирует, что GC будет вызван. По сути, это верный признак плохого кода. Если вы зависите от System.gc() чтобы ваш код работал, то ваш код, вероятно, не работает. В этом случае вы, похоже, полагаетесь на это ради производительности, и это признак того, что ваш код определенно не работает.

Вы никогда не можете быть уверены, что JVM выполнит ваш запрос, и вы не можете сказать, как он будет выполнять сборку мусора. JVM может решить полностью проигнорировать ваш запрос (т. Е. Это не гарантия). Будь то System.gc() будет делать то, что должен, довольно сомнительно. Поскольку его поведение не гарантировано, лучше не использовать его вообще.

Наконец, вы можете отключить явные вызовы System.gc() используя -XX:DisableExplicitGC вариант, который означает, что опять же, это не гарантирует, что ваш System.gc() вызов будет выполнен, потому что он может быть запущен на JVM, которая была настроена на игнорирование этого явного вызова.

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

Вы можете отключить кэширование, создав текстовый файл mstor.properties в корне вашего пути к классам со следующим содержимым:

mstor.cache.disabled=true

Вы также можете установить это значение как системное свойство:

java -Dmstor.cache.disabled=true SomeProgram

Вы не должны полагаться на System.gc(), так как он может быть проигнорирован VM. Если вы получаете OutOfMemory, это означает, что VM уже пыталась запустить GC. Вы можете попробовать увеличить размер кучи, изменить размеры поколений в куче (скажем, большинство ваших объектов заканчивают в старом поколении, тогда вам не нужно много памяти для молодого поколения), просмотрите свой код и убедитесь, что у вас нет ссылок. к ресурсам, которые вам не нужны.

Призвание System.gc() в общем смысле это пустая трата времени, это не гарантирует, что что-то будет сделано в любое время, в лучшем случае это предложение, и в большинстве случаев оно игнорируется. Называя это после OutOfMemoryException еще более бесполезен, потому что JVM уже пыталась восстановить память до того, как было сгенерировано исключение.

Единственное, что вы можете сделать, если используете сторонний код, которым не можете управлять, - это увеличить выделение кучи JVM в командной строке до максимально возможного уровня, который может обработать ваш конкретный компьютер.

Начните работу с памятью Java JVM (куча, стек, -xss -xms -xmx -xmn...)

Другие вопросы по тегам