Стратегия агрессивного сборщика мусора - PullRequest
31 голосов
/ 02 ноября 2011

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

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

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

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

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

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

Для мониторинга я использую в основном 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 

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

Ответы [ 4 ]

20 голосов
/ 02 ноября 2011

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

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

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

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

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

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

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

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

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

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

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

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

3 голосов
/ 29 ноября 2015

Алгоритм 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. Значение будет иметь степень двойки и может варьироваться от 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 Время и результаты потрясающие.

2 голосов
/ 02 ноября 2011

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

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

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

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

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

2 голосов
/ 02 ноября 2011

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

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

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

...