Агрессивная стратегия сборщика мусора

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

В прошлом мы видели следующее при большой нагрузке: Используемое кучное пространство медленно увеличивается, поскольку сборщик мусора собирает меньше объема вновь выделенной памяти, размер используемой кучи медленно растет и в конечном итоге приближается к указанной максимальной куче. В этот момент сборщик мусора сильно ударит и начнет использовать огромное количество ресурсов, чтобы предотвратить превышение максимального размера кучи. Это замедляет приложение вниз (легко 10x как медленное), и в этот момент большую часть времени GC будет успешно очищать мусор через несколько минут или терпеть неудачу и бросать OutOfMemoryException, оба из них на самом деле не приемлемы.

Используемое оборудование представляет собой четырехъядерный процессор с объемом памяти не менее 4 ГБ с 64-разрядным Linux, и все это мы можем использовать в случае необходимости. В настоящее время приложение сильно использует одно ядро, которое использует большую часть своего времени для работы с одним ядром/потоком. Другие ядра в основном простаивают и могут использоваться для сбора мусора.

У меня возникает ощущение, что сборщик мусора должен собираться более агрессивно на ранней стадии, задолго до того, как у него закончится память. Наше приложение не имеет проблем с пропускной способностью, требования к минимальному времени ожидания немного важнее пропускной способности, но гораздо менее важно, чем не приближаться к максимальному размеру кучи. Это приемлемо, если один занятый поток работает только на 75% от текущей скорости, если это означает, что сборщик мусора может идти в ногу с созданием. Короче говоря, неуклонное снижение производительности лучше, чем внезапное падение, которое мы видим сейчас.

Я прочитал Java SE 6 HotSpot [tm] Настройка виртуальной машины Garbage Collection Tuning, что означает, что я хорошо понимаю варианты, однако я по-прежнему трудно выбрать правильные настройки, так как мои требования немного отличаются от того, что обсуждается в документе.

В настоящее время я использую ParallelGC с опцией -XX:GCTimeRatio=4. Это работает немного лучше, чем значение по умолчанию для отношения времени, но у меня есть ощущение, что GC имеет возможность запускать больше, чем это делает.

Для мониторинга я использую jconsole и jvisualvm в основном.

Я хотел бы знать, какие варианты сбора мусора вы рекомендуете для вышеуказанной ситуации. Кроме того, какой GC отладочный вывод я могу посмотреть, чтобы лучше понять шейку бутылки.

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

Я использую:

java version "1.6.0_27-ea"
Java(TM) SE Runtime Environment (build 1.6.0_27-ea-b03)
Java HotSpot(TM) 64-Bit Server VM (build 20.2-b03, mixed mode)

С параметрами JVM:

-Xmx1024M and -XX:GCTimeRatio=4 

Изменить в ответ на комментарии Matts: Большая часть памяти (и процессор) идет на создание объектов, которые представляют текущую ситуацию. Некоторые из них сразу же будут отброшены, так как ситуация быстро меняется, некоторые другие будут иметь средний срок службы, если обновления не появятся некоторое время.

Ответ 1

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

Несколько других точек

  1. Если вы постоянно просачиваете объекты в аренду, потому что вы распределяете скорость быстрее, чем ваш молодой ген может быть пронесен, ваши поколения имеют неправильный размер. Вам нужно будет сделать правильный анализ поведения вашего приложения, чтобы иметь возможность правильно их отсортировать, вы можете использовать visualgc для этого.
  2. Пропускной коллектор предназначен для принятия одной большой паузы в отличие от многих небольших пауз, преимущество в том, что это уплотняющий коллектор и обеспечивает более высокую общую пропускную способность.
  3. CMS существует для обслуживания другого конца спектра, т.е. гораздо более значительных пауз, но более низкой общей пропускной способности. Недостатком является то, что это не уплотнение, поэтому фрагментация может быть проблемой. Проблема фрагментации была улучшена в 6u26, поэтому, если вы не используете эту сборку, это может быть время обновления. Обратите внимание, что эффект "кровопускания на теневой", который вы заметили, усугубляет проблему фрагментации, и, учитывая время, это приведет к сбоям в продвижении (ака незапланированной полной gc и связывает паузу STW). Я ранее написал ответ об этом на этот вопрос
    1. Если вы используете 64-битную JVM s > 4 ГБ оперативной памяти и достаточно недавнюю JVM, убедитесь, что вы -XX:+UseCompressedOops, иначе вы просто теряете пространство, так как 64-битная JVM занимает ~ 1.5x пространство 32-битной JVM для (и если вы этого не сделаете, обновите, чтобы получить доступ к большему количеству оперативной памяти)

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

  • eden достаточно большой, чтобы его не собирали слишком часто.
  • пространства для выживших, размер которых соответствует порогу владения.
  • порог владения, установленный для обеспечения, насколько это возможно, того, чтобы только действительно долговечные объекты превращали его в работу

Поэтому скажите, что у вас была куча 6G, вы можете сделать что-то вроде 5G eden + 16M оставшихся в живых мест + порог владения 1.

Основной процесс

  • выделить в eden
  • eden заполняется
  • живые объекты попали в пространство для оставшихся в живых.
  • живые объекты из оставшегося в живых места либо скопированы в космос, либо переведены в режим ожидания (в зависимости от порога владения и доступного пространства, а также от времени, когда они были скопированы с 1 на другую).
  • все, что осталось в eden, сметено.

Следовательно, при заданных пространствах, соответствующих размеру вашего профиля распределения приложений, вполне возможно настроить систему таким образом, чтобы она хорошо справлялась с нагрузкой. Несколько оговорок к этому;

  • вам нужно провести несколько длительных тестов, чтобы сделать это правильно (например, может потребоваться несколько дней, чтобы поразить проблему фрагментации CMS)
  • вам нужно сделать каждый тест несколько раз, чтобы получить хорошие результаты.
  • вам нужно изменить одну вещь за раз в конфигурации GC
  • вам нужно будет предоставить достаточно повторяемую рабочую нагрузку для приложения, иначе будет сложно объективно сравнить результаты с разных тестовых прогонов.
  • Это будет очень сложно сделать надежно, если рабочая нагрузка непредсказуема и имеет массивные пики/впадины.

Точки 1-3 означают, что это может занять возраст, чтобы получить право. С другой стороны, вы можете быстро сделать это достаточно быстро, это зависит от того, насколько вы анальным!

Наконец, повторив точку зрения Питера Лоури, вы можете сэкономить много хлопот (хотя и ввести какую-то другую проблему), если вы действительно строго относитесь к распределению объектов.

Ответ 2

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

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

Крайним случаем является создание такого маленького мусора, который он не собирает весь день (даже небольшие коллекции). Это возможно для приложения на стороне сервера (но может быть невозможно для приложения GUI)

Ответ 3

Алгоритм G1GC, который был введен со стабильным Java 1.7, преуспевает. Вам нужно просто указать максимальное время паузы, в котором вы хотите жить в своем приложении. JVM позаботится обо всех других вещах для вас.

Ключевые параметры:

-XX:+UseG1GC -XX:MaxGCPauseMillis=1000 

Есть еще несколько параметров для настройки. Если вы используете 4 ГБ ОЗУ, настройте размер региона как 4 ГБ /2048 блоков, что составляет примерно 2 МБ

-XX:G1HeapRegionSize=2  

Если у вас 8-процессорный процессор, настройте еще два параметра

-XX:ParallelGCThreads=4 -XX:ConcGCThreads=2 

Помимо этих параметров, оставьте другие значения параметров по умолчанию, например

-XX:TargetSurvivorRatio и т.д.

Посмотрите сайт oracle для получения более подробной информации о G1GC.

-XX:G1HeapRegionSize=n

Устанавливает размер области G1. Значение будет иметь мощность 2 и может варьироваться от 1 МБ до 32 МБ. Цель состоит в том, чтобы иметь около 2048 регионов на основе минимального размера кучи Java.

 -XX:MaxGCPauseMillis=200

Устанавливает целевое значение для требуемого максимального времени паузы. Значение по умолчанию - 200 миллисекунд. Указанное значение не адаптируется к размеру вашей кучи.

-XX:ParallelGCThreads=n

Устанавливает значение рабочих потоков STW. Устанавливает значение n в число логических процессоров. Значение n совпадает с числом логических процессоров до значения 8.

Если имеется более восьми логических процессоров, устанавливается значение n примерно 5/8 логических процессоров. Это работает в большинстве случаев, за исключением больших систем SPARC, где значение n может составлять приблизительно 5/16 от логических процессоров.

-XX:ConcGCThreads=n

Рекомендации из оракула:

Когда вы оцениваете и точно настраиваете G1 GC, помните следующие рекомендации:

  • Размер молодого поколения. Избегайте явного задания размера молодого поколения с помощью опции -Xmn или любого или другого связанного параметра, такого как -XX:NewRatio. Fixing the size of the young generation overrides the target pause-time goal.

  • Цели паузы времени:. Когда вы оцениваете или настраиваете сборку мусора, всегда существует латентность и компромисс между пропускной способностью. G1 GC представляет собой инкрементный сборщик мусора с равномерными паузами, но также больше накладных расходов на потоки приложений. The throughput goal for the G1 GC is 90 percent application time and 10 percent garbage collection time.

Недавно я заменил CMS алгоритмом G1GC для кучи 4 ГБ с почти равным разделением молодого поколения и старого поколения. Я установил MaxGCPause Время, и результаты будут потрясающими.

Ответ 4

Первые параметры виртуальной машины, которые я попробовал, увеличивают число NewSize и MaxNewSize и используют один из алгоритмов параллельного GC (попробуйте UseConcMarkSweepGC, который предназначен для "сохранения пауз в сборе мусора" ).

Чтобы подтвердить, что паузы, которые вы видите, связаны с GC, включите подробный журнал GC (-verbose:gc -XX:+PrintGCDetails -XX:+PrintGCTimeStamps). Более подробная информация о том, как читать эти журналы, доступна онлайн.

Чтобы понять узкое место, запустите приложение в профилировщике. Сделайте снимок кучи. Затем пусть приложение сделает свое дело какое-то время. Сделайте еще один снимок кучи. Чтобы увидеть, что занимает все пространство, ищите то, что есть намного больше после второго снимка кучи. Visual VM может это сделать, но также рассмотрите MAT.

В качестве альтернативы рассмотрите возможность использования -XX:+HeapDumpOnOutOfMemoryError, чтобы получить моментальный снимок реальной проблемы, и вам не нужно воспроизводить его в другой среде. Сохраненная куча может быть проанализирована с помощью тех же инструментов - MAT и т.д.

Однако вы можете получить OutOfMemoryException либо из-за утечки памяти, либо из-за того, что у вас слишком малый размер кучи. Подробный GC-журнал должен помочь вам ответить на оба этих вопроса.