Рубрики
Без рубрики

Пошаговое руководство по настройке сборки мусора Java

Работа с приложениями Java имеет множество преимуществ. Особенно по сравнению с такими языками, как C/C+… С тегами java, gc, настройка, производительность.

Работа с приложениями Java имеет множество преимуществ. Особенно по сравнению с такими языками, как C/C++. В большинстве случаев вы получаете совместимость между операционными системами и различными средами. Вы можете перемещать свои приложения с сервера на сервер, из операционной системы в операционную систему без особых усилий или в редких случаях с незначительными изменениями.

Одним из наиболее интересных преимуществ запуска приложения на основе JVM является автоматическая обработка памяти. Когда вы создаете объект в своем коде, он назначается в кучу и остается там до тех пор, пока на него не будет дана ссылка из кода. Когда он больше не нужен, его нужно удалить из памяти, чтобы освободить место для новых объектов. В языках программирования, таких как C или C++, очистка памяти выполняется нами, программистами, вручную в коде. В таких языках, как Java или Kotlin, нам не нужно заботиться об этом – это делается автоматически JVM, ее сборщиком мусора.

Настройка сборки мусора GC – это процесс настройки параметров запуска вашего приложения на основе JVM в соответствии с желаемыми результатами. Не больше и не меньше. Это может быть так же просто, как настроить размер кучи – -Xmx и -Xms параметры. С чего, кстати, вам и следует начать. Или это может быть так же сложно, как настроить все дополнительные параметры для настройки различных областей кучи. Все зависит от ситуации и ваших потребностей.

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

Вот почему так важно, чтобы сборщик мусора работал как можно эффективнее. Процесс GC может быть тяжелым. Во время нашей работы в качестве разработчиков и консультантов мы сталкивались с ситуациями, когда сборщик мусора работал в течение 20 секунд в течение 60-секундного промежутка времени. Это означает, что в 33 % случаев приложение не выполняло свою работу — вместо этого оно занималось домашним хозяйством.

Мы можем ожидать, что потоки будут остановлены на очень короткие промежутки времени. Это происходит постоянно:

2019-10-29T10:00:28.879-0100: 0.488: Total time for which application threads were stopped: 0.0001006 seconds, Stopping threads took: 0.0000065 seconds

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

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

Первое, что вы должны знать, это то, что настройка сборки мусора должна быть одной из последних выполняемых вами операций. Если вы не абсолютно уверены, что проблема заключается в сборке мусора, не начинайте с изменения параметров JVM . Говоря прямо, существует множество ситуаций, когда способ работы сборщика мусора только подчеркивает большую проблему.

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

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

На этой диаграмме вы можете увидеть нечто, называемое “зуб акулы”. Обычно это признак здоровой кучи JVM. Самая большая часть памяти, называемая старым поколением, заполняется, а затем очищается сборщиком мусора. Если бы мы сопоставили это с таймингами сборщика мусора, мы бы увидели всю картину в целом. Зная все это, мы можем судить, удовлетворены ли мы тем, как работает сборка мусора, или необходима настройка.

Еще одна вещь, которую вы можете изучить, – это журналы сбора мусора, которые мы обсуждали в Понимание журналов Java GC в блоге. Вы также можете использовать такие инструменты, как jstat или любой профилировщик . Они предоставят вам подробную информацию о том, что происходит внутри вашей виртуальной машины, особенно когда речь заходит о куче памяти и сборке мусора.

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

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

Говоря о процедуре, которую вы должны выполнить при настройке сборщика мусора, вы должны помнить, что в мире JVM доступно больше сборщиков мусора. При работе с меньшими кучами и более старыми версиями JVM, такими как версии 7, 8 или 9, вы, вероятно, будете использовать хороший, старый параллельный сборщик мусора Mark Sweep для кучи старого поколения. С более новой версией JVM, такой как 11, вы, вероятно, используете G1GC. Если вам нравится экспериментировать, вы, вероятно, используете новейшую версию JVM вместе с ZGC. Вы должны помнить, что каждый сборщик мусора работает по-разному. Следовательно, процедура настройки для них будет другой.

Запуск приложения на основе JVM с различными сборщиками мусора – это одно, а проведение экспериментов – другое. Настройка сборки мусора Java потребует множества экспериментов и попыток. Это нормально, что вы не достигнете желаемых результатов с первой попытки. Вам захочется вносить изменения одно за другим и наблюдать, как ваше приложение и сборщик мусора ведут себя после каждого изменения.

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

Запуск настройки GC

Начните с рассмотрения того, как ведет себя ваше приложение, какие события заполняют пространство памяти и какое пространство заполнено. Помните об этом:

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

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

Давайте начнем настройку.

Размер кучи

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

Что следует учитывать при настройке кучи для вашего приложения? Конечно, это зависит от многих факторов. Существуют такие системы, как Apache Solr или Elasticsearch, которые сильно зависят от ввода-вывода и могут совместно использовать кэш файловой системы операционной системы. В таких случаях вы должны оставить как можно больше памяти для операционной системы, особенно если ваши данные большие. Если ваше приложение обрабатывает много данных или выполняет большой анализ, могут потребоваться большие кучи.

В любом случае, вы должны помнить, что до 32 ГБ размера кучи вы получаете выгоду от так называемых сжатых обычных указателей на объекты . Обычные указатели на объекты или ООП – это 64-битные указатели на память. Они указывают на память, позволяющую JVM ссылаться на объекты в куче. По крайней мере, так это работает, не углубляясь глубоко во внутренние органы.

До 32 ГБ размера кучи JVM может сжимать эти операции и, таким образом, экономить память. Вот как вы можете представить сжатый обычный указатель объекта в мире JVM:

Первые 32 бита используются для фактического обращения к памяти и хранятся в куче. 32 бит достаточно для обращения к каждому объекту в кучах объемом до 32 ГБ. Как мы это вычислим? У нас есть 232 – наше пространство, которое может быть адресовано 32-битным указателем. Из-за трех нулей в хвосте нашего указателя у нас есть 232+3, что дает нам 235, то есть 32 ГБ места в памяти, которое можно адресовать. Это максимальный размер кучи, который мы можем использовать со сжатыми обычными указателями на объекты.

Переход выше 32 ГБ кучи приведет к тому, что JVM будет использовать 64-битные указатели . В некоторых случаях при переходе с 32 ГБ кучи на 35 ГБ у вас, скорее всего, будет более или менее одинаковый объем полезного пространства. Это зависит от использования памяти вашего приложения, но вам нужно принять это во внимание и, вероятно, превысить 35 ГБ, чтобы увидеть разницу.

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

Вы можете увидеть размер пула JVM и сводные диаграммы GC. Как вы можете видеть, размер кучи JVM можно охарактеризовать как зубы акулы – здоровый образец. Основываясь на первой диаграмме, мы можем предположить, что для этого приложения нам потребуется не менее 500-600 МБ памяти. Точка, в которой освобождается память, в данном случае составляет около 1,2 ГБ от общего размера кучи для сборщика мусора G1. В этом сценарии сборщик мусора работает около 2 секунд в течение 60 секунд, что означает, что JVM тратит около 2 % времени на сборку мусора. Это хорошо и полезно для здоровья.

Мы также можем посмотреть на среднее время сбора мусора вместе с 99-м и 90-м процентилями:

Основываясь на этой информации, мы можем видеть, что нам не нужна более высокая куча. Сборка мусора быстро и эффективно очищает данные.

С другой стороны, если мы знаем, что наше приложение используется и обрабатывает данные, его куча превышает 70-80 % от максимальной кучи, на которую мы ее установили, и мы увидим, что GC борется, мы знаем, что у нас проблемы. Например, посмотрите на пулы памяти этого приложения:

Вы можете видеть, что что-то начало происходить и что использование памяти постоянно превышает 80 % в пространстве старого поколения . Соотнесите это с работой сборщика мусора:

И вы можете ясно видеть признаки высокой загрузки памяти. Сборщик мусора начал выполнять больше работы, пока память не очищается. Это означает, что, хотя JVM пытается очистить данные, она не может. Это признак грядущих проблем – у нас просто не хватает места в куче для новых объектов. Но имейте в виду, что это также может быть признаком утечки памяти в приложении. Если вы видите рост памяти с течением времени и сборку мусора, которая не может освободить память, возможно, у вас возникла проблема с самим приложением. Что-то, что стоит проверить.

Итак, как нам установить размер кучи? Установив его минимальный и максимальный размер. Минимальный размер задается с помощью параметра -Xms JVM, а максимальный размер задается с помощью параметра -Xmx . Например, чтобы установить размер кучи для нашего приложения размером 2 ГБ мы бы добавили -Xms2g (Xms2g) -Xmx2 g к параметрам запуска нашего приложения. В большинстве случаев я бы также установил для них одинаковое значение, чтобы избежать изменения размера кучи, и в дополнение к этому я бы добавил флаг -XX:+alwayspretouch , а также для загрузки страниц памяти в память при запуске приложения.

Мы также можем контролировать размер пространства кучи молодого поколения с помощью свойства -Xml , как и -Xms и -Xmx . Это позволяет нам явно определять размер пространства кучи молодого поколения, когда это необходимо.

Серийный Сборщик Мусора

Последовательный сборщик мусора – это самый простой однопоточный сборщик мусора. Вы можете включить сборщик мусора Последовательный путем добавления флага -XX:+UseSerialGC в параметры запуска приложения JVM. Мы не будем фокусироваться на настройке серийного сборщика мусора.

Параллельный Сборщик Мусора

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

Настройка параллельного сборщика мусора

Как мы уже упоминали, Параллельный сборщик мусора использует несколько потоков для выполнения своих обязанностей по очистке. Количество потоков , которые может использовать сборщик мусора, задается с помощью флага -XX:ParallelGCThreads , добавленного в параметры запуска нашего приложения.

Например, если бы мы хотели, чтобы 4 потока выполняли сборку мусора, мы бы добавили следующий флаг в параметры нашего приложения: -XX: Параллельные потоки=4 . Имейте в виду, что чем больше потоков вы посвящаете обязанностям по уборке, тем быстрее это может произойти. Но есть и обратная сторона наличия большего количества потоков сбора мусора. Каждый поток GC, участвующий в незначительном событии по сбору мусора, зарезервирует часть кучи арендованного поколения для рекламных акций. Это создаст разделение пространства и фрагментацию. Чем больше потоков, тем выше фрагментация. Сокращение числа параллельных потоков сборки мусора и увеличение размера старого поколения помогут с фрагментацией, если это станет проблемой.

Второй вариант, который можно использовать, это -XX:MaxGCPauseMillis . Он определяет цель максимального времени паузы между двумя последовательными событиями сборки мусора. Он определяется в миллисекундах. Например, с флагом -ХХ: MaxGCPauseMillis=100 мы сообщаем параллельному сборщику мусора, что мы хотели бы иметь максимальную паузу в 100 миллисекунд между сборками мусора. Чем больше промежуток между сборками мусора, тем больше мусора может остаться в куче, что делает следующую сборку мусора более дорогостоящей. С другой стороны, если значение слишком мало, приложение будет тратить большую часть своего времени на сборку мусора вместо выполнения бизнес-логики.

Цель максимальной пропускной способности может быть установлена с помощью флага -XX:GCTimeRatio . Он определяет соотношение между временем, проведенным в GC и время, проведенное за пределами GC . Он определяется как 1/(1 + GC_TIME_RATIO_ ЗНАЧЕНИЕ) и это процент времени, потраченного на сбор мусора.

Например, установка -XX: GCTimeRatio=9 означает, что 10 % рабочего времени приложения может быть потрачено на сборку мусора. Это означает, что приложение должно получить в 9 раз больше рабочего времени по сравнению со временем, отведенным на сбор мусора.

По умолчанию значение флага -XX:GCTimeRatio устанавливается JVM равным 99, что означает, что приложение получит в 99 раз больше рабочего времени по сравнению со сборкой мусора, что является хорошим компромиссом для серверных приложений.

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

  • достигните максимального времени паузы
  • достигайте пропускной способности, только если достигнуто время паузы
  • достичь следа только в том случае, если будут достигнуты первые две цели

Параллельный сборщик мусора увеличивает и сокращает поколения для достижения вышеуказанных целей. Рост и сокращение поколений производится с шагом в фиксированном проценте. По умолчанию генерация увеличивается с шагом 20% и уменьшается с шагом 5%. Каждое поколение настраивается самостоятельно. Процент роста поколения контролируется флагом -XX: Younggenerationsizeincrement . Рост старого поколения контролируется флагом -XX: Tenuredgenerationsizeincrement .

Уменьшающейся частью можно управлять с помощью флага -XX: adaptivesizedecrementscalefactor . Например, процент уменьшающегося прироста для молодого поколения устанавливается путем деления значения -XX: флаг Younggenerationsizeincrement на значение -XX: Адаптивизированный масштабирующий фактор .

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

Параллельный сборщик мусора может создать исключение OutOfMemory , если на сборку мусора тратится слишком много времени. По умолчанию, если более 98 % времени тратится на сборку мусора и восстанавливается менее 2 % кучи, такое исключение будет выдано. Если мы хотим отключить это поведение, мы можем добавить флаг -XX:-usegcoverheadlimit . Но, пожалуйста, имейте в виду, что сборщики мусора, работающие в течение длительного времени и очищающие очень мало или почти совсем не имеющие памяти, обычно означают, что размер вашей кучи слишком мал или ваше приложение страдает от утечек памяти.

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

Сборщик мусора с Одновременной Разметкой

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

Настройка сборщика мусора с одновременной разверткой меток

Как и другие доступные сборщики в мире JVM, сборщик мусора CMS является поколенческим, что означает, что вы можете ожидать двух типов событий – незначительных и крупных коллекций. Идея здесь заключается в том, что большая часть работы будет выполняться параллельно потокам приложений, чтобы предотвратить заполнение штатного поколения. Во время обычной работы большая часть сборки мусора выполняется без остановки потоков приложений. CMS останавливает потоки только на очень короткий промежуток времени в начале и середине коллекции во время основной коллекции. Небольшие коллекции выполняются очень похоже на то, как работает параллельный сборщик мусора – все потоки приложений останавливаются во время сбора данных.

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

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

Сбор арендованного пространства генерации должен быть синхронизирован при использовании сборщика мусора CMS. Потому что сбои в параллельном режиме могут быть дорогостоящими нам нужно правильно настроить запуск |старого поколения куча очистка не попадать в такие события. Мы можем сделать это с помощью флага -XX:cmsinitiating occupancyfraction . Он используется для установки процента использования старого поколения кучи, когда CMS должна начать очистку это. Например, начиная с 75%, мы бы установили упомянутый флаг в -XX:cmsinitiating occupancyfraction=75 . Конечно, это только информативная ценность, и сборщик мусора все равно будет использовать эвристику и попытается определить наилучшую возможную ценность для запуска своей работы по очистке старого поколения. Чтобы избежать использования эвристики, мы можем использовать флаг -XX:+USECMSINITIATING occupancyonly . Таким образом, мы будем придерживаться только процента от параметра -XX:cmsinitiating occupancyfraction .

Поэтому при установке флага -XX:+usecmsinitiating occupancyonly на более высокое значение вы задерживаете очистку пространства старого поколения в куче. Это означает, что ваше приложение будет работать дольше без необходимости подключения CMS для очистки занятого пространства. Но, когда процесс начнется, он может оказаться дороже, потому что у него будет больше работы. С другой стороны, установка флага -XX:+USECMSINITIATING occupancyonly на меньшее значение приведет к более частой очистке поколения CMS, но это может быть быстрее. Какой из них выбрать – это зависит от вашего приложения и должно быть скорректировано в зависимости от конкретного случая использования.

Мы также можем сказать нашему сборщику мусора, чтобы он собрал кучу молодого поколения во время паузы с замечаниями или перед выполнением полного сбора мусора. Первый делается путем добавления флага -XX:+cmsscavengebeforeremark в наши параметры запуска. Второе делается путем добавления флага -XX:+scavengebeforefullgc в параметры запуска нашего приложения. В результате это может повысить производительность сборки мусора, так как ему не нужно будет проверять наличие ссылок между пространствами кучи молодого и старого поколений.

Фаза замечания параллельного сборщика мусора для очистки от меток потенциально может ускорить его. По умолчанию он однопоточный, и, как вы помните, мы упоминали, что он останавливает все потоки приложений. Включив флаг -XX:+cmsparallelremarkenabled в параметры запуска нашего приложения, мы можем заставить фазу примечания использовать несколько потоков. Однако из-за некоторых деталей реализации на самом деле не всегда верно, что параллельная версия фазы замечания будет быстрее по сравнению с однопоточной версией. Это то, что вы должны проверить и протестировать в своей среде.

Подобно параллельному сборщику мусора, параллельный сборщик мусора для очистки меток может выбрасывать Из памяти исключения, если слишком много времени тратится на сборку мусора . По умолчанию, если более 98 % времени тратится на сборку мусора и восстанавливается менее 2 % кучи, будет выдано такое исключение. Если мы хотим отключить это поведение, мы можем добавить флаг -XX:-usegcoverheadlimit . Разница по сравнению с параллельным сборщиком мусора заключается в том, что время , которое считается равным 98% , учитывается только тогда, когда потоки приложений остановлены .

Сборщик мусора G1

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

Настройка Сборщика мусора G1

Есть также две вещи, о которых стоит упомянуть. Сборщик мусора G1 пытается выполнять более длительные операции параллельно, не останавливая потоки приложений. Быстрые операции будут выполняться быстрее, когда потоки приложений будут приостановлены. Таким образом, это еще одна реализация в основном параллельных алгоритмов сбора мусора.

Сборщик мусора G1 очищает память в основном способом эвакуации . Живые объекты из одной области памяти копируются в новую область и по пути уплотняются. После завершения процесса область памяти, из которой был скопирован объект, снова доступна для выделения объекта.

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

Фаза только для молодежи начинается с нескольких коллекций молодого поколения, которые продвигают объекты для поколения, состоящего в штате. Эта фаза активна до тех пор, пока пространство старого поколения не достигнет определенного порога. По умолчанию загрузка составляет 45 %, и мы можем контролировать это, установив -XX: Инициирующий флаг Heapoccupancypercent и его значение. Как только этот порог достигнут, G1 запускает другую коллекцию молодого поколения, которая называется одновременный запуск . Флаг -XX:Initiating Heapoccupancypercent , который управляет Начальной меткой коллекции, является начальным значением, которое дополнительно корректируется сборщиком мусора. Чтобы отключить настройки, добавьте -XX:-g1useadaptiveihop установите флажок для параметров запуска JVM.

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

Этап восстановления пространства содержит несколько смешанных сборок мусора, которые работают как в областях молодого, так и старого поколения пространства кучи G1GC. Этап освоения пространства заканчивается, когда G1GC видит, что эвакуация большего количества регионов старого поколения не даст достаточно свободного места, чтобы сделать усилия по восстановлению пространства стоящими. Его можно установить с помощью значения флага -XX: g1heapwastepercent .

Мы также можем контролировать, по крайней мере в какой-то степени, будет ли выполняться периодическая сборка мусора. Используя флаг -XX:G 1 Периодическая загрузка системы сбора мусора , мы можем установить среднюю нагрузку, выше которой периодическая сборка мусора выполняться не будет. Например, если ваша система загружена 10 в последнюю минуту, и мы установили флаг -XX:G 1 Периодический порог загрузки системы GC=10 , периодическая сборка мусора выполняться не будет.

Сборщик мусора G1, кроме -Xmx и -Xms флаги, позволяет нам использовать набор флагов для определения размера кучи и ее областей. Мы можем использовать -XX:minheapfreeratio , чтобы указать сборщику мусора соотношение свободной памяти, которое должно быть достигнуто, и флаг -XX:MaxHeapFreeRatio , чтобы установить желаемое максимальное соотношение свободной памяти в куче. Мы также знаем, что G1GC пытается сохранить размер молодого поколения между значениями -XX: G1NewSizePercent и -ХХ: G1MaxNewSizePercent . Это также определяет время паузы. Уменьшение размера может ускорить процесс сбора мусора за счет меньших затрат труда. Мы также можем установить строгий размер молодого поколения, используя флаги -XX:NewSize и -XX:MaxNewSize .

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

Когда настройка для сборки мусора задержка мы должны свести время паузы к минимуму. Это означает, что в большинстве случаев -Xmx и -Xms значения должны быть установлены на одно и то же значение, и мы также должны загружать страницы памяти во время запуска приложения с помощью флага -XX:+alwayspretouch .

Если ваша фаза только для молодых занимает слишком много времени, это признак того, что уменьшение значения -XX:G1NewSizePercent (по умолчанию 5) является хорошей идеей. В некоторых случаях также может помочь уменьшение -XX: g1maxnewsizepercent (по умолчанию 60). Если смешанные коллекции занимают слишком много времени, нам рекомендуется увеличить значение флага -XX:g1mixedgccounttarget , чтобы распространить GC поколения с фиксированным сроком службы на большее количество коллекций. Увеличьте -XX: g1heapwastepercent , чтобы остановить сбор мусора старого поколения раньше. Вы также можете изменить -XX: G1MixedGCLiveThresholdPercent – значение по умолчанию равно 65 и определяет порог занятости, выше которого куча старого поколения будет включена в смешанную коллекцию. Увеличение этого значения приведет к тому, что при сборке мусора будут опущены менее занятые области пространства старого поколения при выполнении смешанной сборки. Регионам, в которых много объектов, требуется больше времени для сбора мусора. Используя упомянутый флаг, мы можем избежать установки этих регионов в качестве кандидатов на сбор мусора. Если вы видите большое время обновления и сканирования RS, может помочь уменьшение значения флага -XX:g1rsetupdatingpausetimepercent , включая флаг -XX:-reduceinitialcardmarks и увеличение флага -XX:g1rsetregionentries . Существует также один дополнительный флаг -XX: Maxgcpausemillis (по умолчанию 250), который определяет максимальное желаемое время паузы. Если вы хотите сократить время паузы, снижение значения также может помочь.

Когда настройка пропускной способности мы хотим, чтобы сборщик мусора очистил как можно больше мусора. В основном в случае систем, которые обрабатывают и хранят много данных. Первое, на что вам следует пойти, – это увеличить значение -XX: MaxGCPauseMillis . Делая это, мы расслабляем сборщика мусора. Это позволяет ему работать дольше для обработки большего количества объектов в куче. Однако этого может быть недостаточно. В таких случаях должно помочь увеличение значения флага -XX:G1NewSizePercent . В некоторых случаях пропускная способность может быть ограничена размером регионов молодого поколения – в таких случаях должно помочь увеличение значения флага -XX:g1maxnewsizepercent .

Мы также можем уменьшить параллелизм, который требует много работы от центрального процессора. Использование флага -XX:g1rsetupdatingpausetimepercent и увеличение его значения позволят увеличить объем работы при приостановке потоков приложения и сократят время, затрачиваемое на параллельные части фазы. Также аналогично настройке задержки, вы можете сохранить флаги -Xmx и -Xms на одном и том же значении, чтобы избежать изменения размера кучи. Загрузите страницы памяти в память с помощью флага -XX:+Alwayspretouch и флага -XX:+uselargepages . Но, пожалуйста, не забывайте применять изменения одно за другим и сравнивать результаты, чтобы вы понимали, что происходит.

Наконец, мы можем настроить на размер кучи . Есть единственный вариант, о котором мы можем подумать здесь, -XX: GCTimeRatio (по умолчанию 12). Он определяет соотношение времени, затрачиваемого на сборку мусора, по сравнению с потоками приложений, выполняющими свою работу, и рассчитывается как 1/(1 + GCTimeRatio) . Значение по умолчанию приведет к тому, что около 8 % рабочего времени приложения будет потрачено на сборку мусора, что больше, чем при параллельной сборке мусора. Больше времени на сборку мусора позволит освободить больше места в куче, но это сильно зависит от приложения, и трудно дать общий совет. Поэкспериментируйте, чтобы найти значение, соответствующее вашим потребностям.

Существуют также общие настраиваемые параметры для сборщика мусора G1. Мы можем контролировать степень распараллеливания при использовании этого сборщика мусора. Включив флаг -XX:+ParallelRefProcEnabled и изменив значение флага -XX:Ссылки на поток . Для каждого N ссылок, определенных флагом -XX: Ссылки на поток , будет использоваться один поток. Если установить это значение равным 0, сборщик мусора G1 всегда будет использовать количество потоков, указанное значением флага -XX:ParallelGCThreads . Для большего распараллеливания уменьшите значение флага -XX: Ссылки на поток . Это должно ускорить параллельные части сборки мусора.

Z Сборщик мусора

Все еще экспериментальная, очень масштабируемая реализация с низкой задержкой. Если вы хотите поэкспериментировать с этим сборщиком мусора Z, вы должны использовать JDK 11 или новее и добавить флаг -XX:+USEZGC в параметры запуска приложения вместе с флагом -XX:+unlockexperimentalvmoptions , поскольку сборщик мусора Z все еще считается экспериментальным.

Настройка сборщика мусора Z

Существует не так много параметров, с которыми мы можем поиграть, когда дело доходит до сборщика мусора Z. Как указано в документации, наиболее важным параметром здесь является максимальный размер кучи, поэтому флаг -Xmx. Поскольку сборщик мусора Z является параллельным сборщиком, размер кучи должен быть скорректирован таким образом, чтобы он мог содержать текущий набор объектов вашего приложения и позволял распределять ресурсы во время работы сборщика мусора. Это означает, что размер кучи может потребоваться больше по сравнению с другими сборщиками мусора, и чем больше памяти вы выделяете для кучи, тем лучших результатов вы можете ожидать от сборщика мусора.

Второй вариант, который вы можете ожидать, – это, конечно, количество потоков, которые будет использовать сборщик мусора Z. В конце концов, это параллельный коллектор, поэтому он может использовать более одного потока. Мы можем установить количество потоков, которые будет использовать сборщик мусора Z, используя флаг -XX:ConcGCThreads . Сам коллектор использует эвристику для выбора правильного количества потоков, которые он должен использовать, но, как обычно, это сильно зависит от приложения, и в некоторых случаях установка этого числа в статическое значение может принести лучшие результаты. Однако это необходимо проверить, так как это очень зависит от конкретного случая использования. Однако есть две вещи, которые следует помнить. Если вы назначите слишком много потоков для сборщика мусора, у вашего приложения может не хватить вычислительной мощности для выполнения своей работы. Установите небольшое количество потоков сборщика мусора, и мусор может быть собран недостаточно быстро. Примите это во внимание при настройке.

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

Статистика JVM, Вызывающая Длительные Паузы При Сборке Мусора

Некоторые люди сообщили, что в системах Linux при высокой загрузке ввода-вывода сборка мусора может приостанавливать потоки на длительный период времени. Вероятно, это вызвано тем, что JVM использует сопоставленный с памятью файл под названием hsperfdata. Этот файл записывается в каталог/tmp и используется для хранения статистики и безопасных точек. Указанный файл обновляется во время GC. В Linux изменение файла, сопоставленного с памятью, может быть заблокировано до завершения ввода-вывода. Как вы можете себе представить, такая операция может занять более длительный период времени, предположительно сотни миллисекунд.

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

[Times: user=0.13 sys=0.11, real=5.45 secs]

Если ваша система сильно основана на вводе-выводе и вы видите упомянутое поведение, вы можете переместить путь к своим журналам GC и tmpfs на быстрый SSD-накопитель. В последних версиях JDK временный каталог, используемый Java, жестко закодирован, поэтому мы не можем использовать – Djava.io.tmpdir чтобы изменить это. Вы также можете включить флаг -XX:+perfdisablesharedmem в параметры приложения JVM. Вы должны знать, что включение этой опции приведет к нарушению работы инструментов, использующих статистику из файла hsperfdata. Например, j статистика не будет работать.

Вы можете прочитать больше об этой проблеме в сообщении в блоге команды Linkedin engineering .

Сброс кучи при исключении нехватки памяти

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

Чтобы избежать потери информации, которая может помочь нам в диагностике проблем, мы можем поручить JVM создать дамп кучи, когда произойдет ошибка OutOfMemory. Мы делаем это, включая флаг -XX:+HeapDumpOnOutOfMemoryError . Мы также можем указать, где должны храниться кучи, используя флаг -XX:HeapDumpPath и установив его значение в местоположение, в которое мы хотим записать дамп кучи. Например: -XX: Путь к куче=/tmp/heapdump.hprof .

Имейте в виду, что файл дампа кучи может быть очень большим – размером с размер вашей кучи. Поэтому вам необходимо учитывать это при указании пути, по которому должен быть записан файл. Мы видели ситуации, когда JVM не могла записать файл дампа кучи объемом 64 ГБ в целевую файловую систему.

Для анализа файла существуют инструменты, которые вы можете использовать. Существуют инструменты с открытым исходным кодом, такие как MAT , и проприетарные инструменты, такие как YourKit Java Profiler или JProfiler . Есть также такие услуги, как дешевые hero.io это может помочь вам в анализе, в то время как более старые версии дистрибутива Oracle JDK поставляются с jhat – инструментом анализа кучи Java . Выберите тот, который вам нравится и соответствует вашим потребностям.

Использование -XX:+агрессивные варианты

Флаг -XX:+Aggressiveopts включает дополнительные флаги, которые, как доказано, приводят к повышению производительности во время набора тестов. Эти флаги могут меняться от версии к версии и содержать такие параметры, как большой кэш автобоксов и удаление агрессивных автобоксов. Это также включает в себя отключение задержки смещенной блокировки. Следует ли вам использовать этот флаг? Это зависит от вашего варианта использования и вашей производственной системы. Как обычно, протестируйте в своей среде, сравните экземпляры с флагом и без него и посмотрите, насколько велика разница.

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

Помните, что мы коснулись только верхушки айсберга, когда речь заходит о настройке сборщиков мусора в мире JVM. Мы упомянули только ограниченное количество доступных флагов, которые вы можете включать/выключать и настраивать. Для получения дополнительного контекста и обучения я предлагаю перейти к Руководству по настройке сборки мусора виртуальной машины Oracle HotSpot и прочитать части, которые, по вашему мнению, могут вас заинтересовать. Посмотрите на свои журналы сбора мусора, проанализируйте их, попытайтесь понять их. Это поможет вам понять вашу среду и то, что происходит внутри JVM, когда собирается мусор. В дополнение к этому, много экспериментируйте! Поэкспериментируйте в своей тестовой среде, на компьютерах разработчиков, поэкспериментируйте в некоторых производственных или предпроизводственных экземплярах и понаблюдайте за различиями в поведении.

Надеюсь, эта статья поможет вам на пути к здоровой сборке мусора в ваших приложениях на основе JVM. Удачи!

Оригинал: “https://dev.to/sematext/a-step-by-step-guide-to-java-garbage-collection-tuning-2m1g”