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

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

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

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

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

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

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

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

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

РЕДАКТИРОВАТЬ: я понимаю, очень хороший вариант здесь, чтобы создать меньше мусора, это то, что мы действительно рассматриваем, однако я хотел бы знать, как мы можем решить эту проблему с помощью настройки 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: Большая часть памяти (и ЦП) идет на создание объектов, которые представляют текущую ситуацию. Некоторые из них будут сразу же отброшены, так как ситуация быстро изменится, другие будут иметь средний срок службы, если в течение некоторого времени не будет обновлений.

4 ответа

Решение

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

Несколько других моментов

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

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

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

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

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

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

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

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

Очки 1-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 и т.п.

Посмотрите на веб-сайте оракула для получения более подробной информации о G1GC,

-XX:G1HeapRegionSize=n

Устанавливает размер области G1. Значение будет иметь степень двойки и может варьироваться от 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, помните о следующих рекомендациях:

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

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

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

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

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

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

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

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

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

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

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

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