Способы уменьшить отток памяти

Задний план

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

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

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

Настраивать

JDK 1.6.18
Весенний пакет 2.1.x
4-ядерный компьютер с оперативной памятью 16 ГБ

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

Проблема

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

В ходе обработки одного этого файла у моего Tenured Generation все в порядке. Он увеличивается максимум до 3 ГБ, и мне никогда не нужно делать один полный сборщик мусора.

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

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


person bwawok    schedule 19.06.2010    source источник
comment
Можно ли опубликовать небольшой автономный пример взбиваемого кода?   -  person President James K. Polk    schedule 20.06.2010
comment
Не берите его с собой... но он кажется довольно стандартным. Прочитайте строку из файла, сохраните как строку и поместите в список. Когда в списке будет 1000 таких строк, поместите их в очередь для чтения рабочими потоками. Упомянутый рабочий поток создает объект домена, отделяет кучу значений от строки, чтобы установить поля (int, long, java.util.Date или String), и передает объект домена в модуль записи jdbc spring по умолчанию ( см. static.springsource.org/spring -batch/apidocs/org/)   -  person bwawok    schedule 20.06.2010


Ответы (7)


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

Я всегда поражаюсь, как много строк генерируется.

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

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

person Peter Tillemans    schedule 19.06.2010
comment
Помогает ли jvisualvm (можно использовать с этим вопросом, это Java6) в выявлении этих проблем? - person Donal Fellows; 20.06.2010
comment
Хорошие идеи, я попробую профилировщик Neatbeans и jvisulvm. Я любитель затмений, но мне никогда не везло с PPTP. - person bwawok; 20.06.2010
comment
Итак.. 90% моей общей памяти находится в char[] в oracle.sql.converter.toOracleStringWithReplacement Так что это сужает его, но не уверен, как сузить его дальше, или если что-то вроде шаблона приспособленца уменьшит память здесь. - person bwawok; 22.06.2010

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

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

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

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

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


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

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

person Stephen C    schedule 20.06.2010
comment
Весенняя партия уже выполняет пакетную обработку для меня. Я знаю, что это не все, чтобы сделать программу на 100% быстрее... но время, затрачиваемое на сборщик мусора, составляет более 5%, может быть, даже 6% или 7%. Время настенных часов могло бы быть лучше, и это помогло бы мне... - person bwawok; 20.06.2010

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

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

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

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

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

person Tobias P.    schedule 19.06.2010
comment
Я бы не стал использовать здесь UseConcMarkSweepGC, время отклика не имеет значения для пакетной обработки (см. ="nofollow noreferrer">java.sun.com/javase/technologies/hotspot/gc/). В любом случае, я не думаю, что проблема в алгоритме GC. - person Pascal Thivent; 20.06.2010
comment
Я попробовал развертку conr mark и потерял около 10% своей производительности. Я согласен, что это не очень хорошо для пакетной обработки. - person bwawok; 20.06.2010
comment
Я использовал термины «молодой» и «постоянный» для обозначения новых и старых, возвращенных мне jmap -heap. Я, наверное, где-то напутал терминологию. У меня есть 16 ГБ оперативной памяти, и если я смогу перейти с 2 ГБ памяти на 12 ГБ и получить ускорение на 5-10%, это того стоит. Не уверен, что вижу вескую причину сбивать память. Я обмениваю 10 медленных GC на 100 быстрых GC... но трачу столько же времени на GC. Я думаю, что мне нужно уменьшить церковь, а не размер ньюгена, чтобы увеличить скорость... - person bwawok; 20.06.2010
comment
Что касается проблемы с утечкой памяти. Может быть, но не думаю, что это вызывает мою проблему. Я кеширую 1-2 ГБ данных перед пакетной обработкой, поэтому 3-3,5 ГБ в старом поколении для меня не проблема. Моя рабочая очередь заполняется, но она ограничена java.util.concurrent.BlockingQueue, поэтому я уверен, что в любой момент времени в памяти находится не более ~10% файла. - person bwawok; 20.06.2010

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

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

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

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

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

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

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

person Pascal Thivent    schedule 19.06.2010
comment
Хорошая идея, дам эргономику на выбор и сравню результаты с тем, что у меня есть. Однако я знаю, что по умолчанию он начинается с очень маленькой кучи и выполняет gc, gc, растущая куча, gc, gc, растущая куча, gc, gc, растущая куча... это совершенно дерьмово. Я думаю, что значительно сократил время прогона, запустив XMS и XMX с желаемого размера. - person bwawok; 20.06.2010
comment
@bwawok: я не хотел сказать, не переопределяйте -Xms и -Xmx - person Pascal Thivent; 20.06.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.

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

person Roland Illig    schedule 19.06.2010
comment
Имеет смысл только в том случае, если у вас есть повторение строк, особенно в пакете. В противном случае вы не помогаете. (И вообще не используйте String.intern, если вы знаете, что это полезно в конкретном случае, с которым вы имеете дело; интернирование — это оптимизация…) - person Donal Fellows; 20.06.2010
comment
1) Я пробовал новое соотношение 2 (по умолчанию), а также 4 и 6. Ничего из этого не помогло. Мои GC были немного быстрее, но случались чаще. 10 сборщиков мусора по 5 ГБ каждый занимают примерно столько же времени, сколько 100 сборщиков мусора по 500 МБ каждый (я думаю, что более крупные сборщики мусора могли тестировать немного быстрее) - person bwawok; 20.06.2010
comment
2) Ни одна строка не должна быть дубликатом или, по крайней мере, их должно быть не очень много. Я знаю несколько частей файла, которые являются одним из трех возможных вариантов... Я мог бы специально пройти стажировку по ним. Не уверен, что это микрооптимизация. Не беспокоюсь о какой-то церкви, просто в 10 раз больше моего набора данных - person bwawok; 20.06.2010

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

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

person irreputable    schedule 19.06.2010
comment
а) Я предварительно кэширую хэш-карту данных, то есть около 1-2 ГБ данных (отсюда и материал, который живет в старом поколении). б) у меня много памяти и 16 потоков, у этой программы есть весь сервер для работы, не беспокоясь о пустой трате памяти - person bwawok; 20.06.2010
comment
Тот факт, что на этом сервере не запущен другой процесс, не означает, что ваша программа должна выделять всю память. Вы должны дать ему столько памяти, сколько ему нужно, и немного больше для непредвиденных обстоятельств. Таким образом, сборщик мусора не должен хранить объекты дольше, чем это необходимо. - person Roland Illig; 20.06.2010
comment
люди говорят, что GC очень плохо работает с кучей больше пары ГБ. Я не понимаю, почему - GC работает только с живыми объектами, так какая разница, сколько мертвых объектов - но это то, что говорят люди. - person irreputable; 20.06.2010
comment
@irreputable - это из-за стоимости обхода объектов, не являющихся мусором, для определения того, где находится мусор. И когда куча заполнена, нужно пройти множество объектов, не являющихся мусором. (И объекты со слабыми ссылками считаются не мусором на этапе маркировки.) - person Stephen C; 20.12.2012