Способы уменьшения оттока памяти - PullRequest
10 голосов
/ 20 июня 2010

Фон

У меня есть пакетная программа Spring, которая читает файл (пример файла, с которым я работаю, имеет размер ~ 4 ГБ), выполняет небольшой объем обработки файла и затем записывает его в базу данных Oracle.

Моя программа использует 1 поток для чтения файла и 12 рабочих потоков для обработки и передачи базы данных.

Я перелистываю много-много-много памяти молодого поколения, из-за чего моя программа работает медленнее, чем я думаю.

Настройка

JDK 1.6.18
Пружинная партия 2.1.х
4-ядерный станок с 16 ГБ оперативной памяти

-Xmx12G 
-Xms12G 
-NewRatio=1 
-XX:+UseParallelGC
-XX:+UseParallelOldGC

Задача

С этими параметрами JVM я получаю около 5x ГБ памяти для Tenured Generation и около 5X ГБ памяти для Young Generation.

В процессе обработки этого одного файла у меня есть постоянное поколение. Максимальный объем может достигать 3 ГБ, и мне никогда не нужно делать один полный сборщик мусора.

Однако молодое поколение достигает максимума много раз. Он достигает диапазона 5 ГБ, а затем происходит параллельный второстепенный сборщик мусора и очищает Young Gen до 500 МБ. Незначительные GC хороши и лучше, чем полноценные GC, но они все еще сильно замедляют мою программу (я почти уверен, что приложение все еще зависает, когда происходит сбор молодого поколения, потому что я вижу, что активность базы данных прекращается). Я трачу более 5% времени моей программы, замороженной для второстепенных групп, и это кажется чрезмерным. Я бы сказал, в ходе обработки этого файла 4 ГБ, я перетаскиваю 50-60 ГБ памяти молодого поколения .

Я не вижу явных недостатков в моей программе. Я пытаюсь подчиняться общим принципам ОО и писать чистый Java-код. Я пытаюсь не создавать объекты без причины. Я использую пулы потоков и, по возможности, передаю объекты вместо создания новых объектов. Я собираюсь начать профилирование приложения, но Мне было интересно, есть ли у кого-нибудь хорошие общие правила или анти-паттерны, чтобы избежать этого, что приведет к чрезмерному оттоку памяти ? Является ли 50-60 ГБ оттока памяти для обработки файла 4 ГБ лучшим, что я могу сделать? Должен ли я вернуться к трюкам JDk 1.2, таким как Object Pooling? (хотя Брайан Гетц выступил с докладом, в котором говорилось о том, почему объединение объектов глупо, и нам больше не нужно это делать. Я доверяю ему гораздо больше, чем себе… :))

Ответы [ 7 ]

9 голосов
/ 20 июня 2010

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

Я трачу более 5% времени моей программы, замороженной на второстепенные GC, и это кажется чрезмерным.

Переверни это. Вы тратите чуть менее 95% времени вашей программы на полезную работу. Или, другими словами, даже если вам удалось оптимизировать GC для работы в НУЛЕЕ, лучшее, что вы можете получить, - это улучшение на 5%.

Если ваше приложение предъявляет жесткие временные требования, на которые влияет время паузы, вы можете рассмотреть возможность использования коллектора с низкой паузой. (Имейте в виду, что уменьшение времени паузы увеличивает общие накладные расходы ГХ ...) Однако для пакетного задания время паузы ГХ не должно быть релевантным.

Что, вероятно, наиболее важно, так это время настенных часов для всей пакетной работы. И (примерно) 95% времени, затрачиваемого на выполнение конкретных приложений, - это то, где вы, вероятно, получите больше отдачи от ваших усилий по профилированию / целевой оптимизации. Например, вы смотрели на пакет обновления, которые вы отправляете в базу данных?


Итак .. 90% моей общей памяти занято в char [] в "oracle.sql.converter.toOracleStringWithReplacement"

Это может указывать на то, что большая часть использования вашей памяти происходит в драйверах Oracle JDBC при подготовке материала для отправки в базу данных. Ты очень мало об этом. Я бы записал это как неизбежные накладные расходы.

3 голосов
/ 20 июня 2010

Было бы очень полезно, если бы вы разъяснили свои термины «молодое» и «опытное» поколение, потому что Java 6 имеет немного другую модель GC: Eden, S0 + S1, Old, Perm

Экспериментировали ли вы с различными алгоритмами сбора мусора? Как выполняются «UseConcMarkSweepGC» или «UseParNewGC».

И не забывайте, просто увеличение доступного пространства НЕ является решением, потому что запуск gc займет намного больше времени, уменьшите размер до нормальных значений;)

Вы уверены, что у вас нет утечек памяти? В модели «потребитель-производитель», - вы описываете, - редко встречаются данные в Old Genue, потому что эти задания обрабатываются очень быстро, а затем «выбрасываются», или ваша рабочая очередь заполняется?

Вы должны обязательно наблюдать за вашей программой с помощью анализатора памяти.

2 голосов
/ 20 июня 2010

Вам необходимо профилировать свое приложение, чтобы точно видеть, что происходит.И я также попытался бы сначала использовать эргономику функцию JVM, как рекомендовано:

2.Эргономика

Функция, называемая здесь эргономикой, была представлена ​​в J2SE 5.0.Цель эргономики - обеспечить хорошую производительность при минимальной или нулевой настройке параметров командной строки, выбрав сборщик мусора

  • , размер кучи
  • ,
  • и время выполнения.компилятор

при запуске JVM вместо использования фиксированных значений по умолчанию.Этот выбор предполагает, что класс компьютера, на котором запущено приложение, является подсказкой относительно характеристик приложения (т. Е. Большие приложения выполняются на больших машинах).В дополнение к этому выбору упрощенный способ настройки сборки мусора.С помощью параллельного коллектора пользователь может указать цели для максимального времени паузы и желаемой пропускной способности для приложения.Это в отличие от указания размера кучи, необходимой для хорошей производительности.Это предназначено, чтобы особенно улучшить производительность больших приложений, которые используют большие кучи.Более общая эргономика описана в документе под названием «Эргономика в виртуальной машине Java 5.0». Рекомендуется попробовать эргономику, представленную в этом последнем документе, перед использованием более подробных элементов управления, объясненных в этом документе .

В этот документ включены функции эргономики, предоставляемые как частьполитика адаптивных размеров для параллельного коллектора.Это включает в себя параметры для указания целей для производительности сборки мусора и дополнительные параметры для тонкой настройки этой производительности.

См. Более подробный раздел о Эргономика в JavaРуководство по настройке сборки мусора виртуальной машины SE 6 HotSpot [tm] .

2 голосов
/ 20 июня 2010

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

Меня всегда удивляет, сколько строк генерируется.

Для доменных объектов перекрестная ссылка на них также показательна. Если вы вдруг увидите в 3 раза больше объектов из производного объекта, чем из источника, то там что-то происходит.

У Netbeans есть хороший инструмент. Я использовал JProfiler в прошлом. Я думаю, что если вы достаточно долго гремите на затмении, вы можете получить ту же информацию из инструментов PPTP.

1 голос
/ 20 июня 2010

Я предполагаю, что с таким ограничением памяти вы должны полностью прочитать файл в память перед выполнением обработки. Не могли бы вы использовать вместо этого java.io.RandomAccessFile ?

1 голос
/ 20 июня 2010

Чтение строки из файла, сохранение в виде строки и внесение в список.Когда в списке будет 1000 таких строк, поместите его в очередь для чтения рабочими потоками.Пусть рабочий поток создает объект домена, снимает кучу значений со строки, чтобы установить поля (int, long, java.util.Date или String), и передает объект домена в стандартную подпружиненную программу записи jdbc

если это ваша программа, почему бы не установить меньший объем памяти, например 256 МБ?

1 голос
/ 20 июня 2010

По моему мнению, молодое поколение не должно быть таким же большим, как старое, чтобы небольшие сборщики мусора оставались быстрыми.

Много ли у вас объектов, представляющих одно и то же значение? Если вы это сделаете, объедините эти дубликаты объектов с помощью простого HashMap:

public class MemorySavingUtils {

    ConcurrentHashMap<String, String> knownStrings = new ConcurrentHashMap<String, String>();

    public String unique(String s) {
        return knownStrings.putIfAbsent(s, s);
    }

    public void clear() {
        knownStrings.clear();
    }
}

С компилятором Sun Hotspot нативный String.intern() очень медленный для большого числа строк, поэтому я предлагаю создать свой собственный String interner.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...