Garbage collector ratiborus как пользоваться

Обновлено: 02.07.2024

Количественные характеристики оценки эффективности GC

Рассмотрим следующие показатели:

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

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

Основные принципы настройки GC
  • Необходимо стремиться к тому, чтобы максимальное количество объектов очищалось при работе малого GC(minor grabage collection). Этот принцип позволяет уменьшить число и частоту работы полного сборщика мусора(full garbage collection) — чья работа является основной причиной больших задержек в приложении
  • Чем больше памяти выделено приложению, тем лучше работает сборка мусора и тем лучше достигаются количественные характеристики по пропускной способности и времени отклика
  • Эффективно настроить можно только 2 из 3 количественных характеристик — пропускная способность, время отклика, размер выделенной памяти — под эффективным значением размера необходимой памяти понимается её минимизация

Рассмотрим пример простого приложения(которое, к примеру, может эмулировать работу вэб-приложения, в ходе которого идёт обращение к БД и накопление возвращаемого результат), в котором в несколько потоков идёт обращение к методу makeObjects(), в ходе которого в цикле непрерывно формируется объект, занимающий определённый объём в куче, затем с ним происходят какие-либо вычисления — делается задержка, ссылка на объект при этом не утекает из метода и по его завершению GC может понять, что данный объект подлежит очистке.

Запускать эксперимент мы будем на:

Для которого по умолчанию включен режим — server и UseParallelGC(многопоточная работа фазы малой сборки мусора)

Для оценки общей величины паузы сборщика мусора можно запускать в режиме:

И суммировать задержку по логу gc.log:

Где real=0.01 secs — реальное время, затраченное на сборку.

А можно воспользоваться утилитой VisualVm, с установленным плагином VisualGC, в котором наглядно можно наблюдать распределение памяти по различным областям GC(Eden, Survivor1, Survivor2, Old) и видеть статистику по запуску и длительности сборки мусора.

Определение размера необходимой памяти

Для начала мы должны запустить приложение с возможно большим размером памяти, чем это это реально необходимо приложению. Если мы не знаем изначально, сколько будет занимать наше приложение в памяти — можно запустить приложение без указания -Xmx и -Xms и HotSpot VM сама выберет размер памяти. Если при старте приложения мы получим OutOfMemory(Java heap space или PermGen space), то мы можем итеративно увеличивать размер доступной памяти(-Xmx или -XX:PermSize) до тех пор пока ошибки не уйдут.
Следующим шагом будет вычисление размера долго-живущих живых данных — это размер old и permanent областей кучи после фазы полной сборки мусора. Этот размер — примерный объём памяти, необходимый для функционирования приложения, для его получения можно посмотреть на размер областей после серии полной сборки. Как правило размер необходимой памяти для приложения -Xms и -Xmx в 3-4 раза больше, чем объём живых данных. Так, для лога, указанного выше — величина old области после фазы полной сборки мусора — 349363K. Тогда предлагаемое значение -Xmx и -Xms

1400 Мб. -XX:PermSize and -XX:MaxPermSize — в 1.5 раз больше, чем PermGenSize после фазы полной сборки мусора — 13324K

20 Мб. Размер young generation принимаю равным 1-1.5 размера объёма живых данных

525 Мб. Тогда получаем строку запуска jvm с такими параметрами:

В VisualVm получаем такую картину:


Всего за 30 сек эксперимента было произведено 54 сборки — 31 малых и 23 полных — с общим временем остановки 3,227c. Данная величина задержки может не удовлетворять необходимым требованиям — посмотрим, сможем ли мы улучшить ситуацию без изменения кода приложения.

Настройка допустимого времени отклика
  • Измерение длительности малой сборки мусора
  • Измерение частоты малой сборки мусора
  • Измерение длительности худшего случая полной сборки мусора
  • Измерение частоты худшего случая полной сборки мусора
Корректировка размера young и old generation

Время, необходимое для осуществления фазы малой сборки мусора, напрямую зависит от числа объектов в young generation, чем меньше его размер — тем меньше длительность, но при этом возрастает частота, т.к. область начинает чаще заполняться. Попробуем уменьшить время каждой малой сборки, уменьшив размер young generation, сохранив при этом размер old generation. Примерно можно оценить, что каждую секунду мы должны очищать в young generation 50потоков*8объектов*1Мб

400Мб. Запустим с параметрами:

В VisualVm получаем такую картину:


На общее время работы малой сборки мусора мы повлиять не смогли — 1,533с — увеличилась частота малых сборок, но общее время ухудшилось — 3,661 из-за того, что увеличилась скорость заполнения old generation и увеличилась частота вызова полной сборки мусора. Чтобы побороть это — попробуем увеличить размер old generation — запустим jvm с параметрами:


Общая пауза теперь улучшилась и составляет 2,637 с а общее значение необходимой для приложения памяти при этом уменьшилось — таким образом итеративно можно найти правильный баланс между old и young generation для распределения времени жизни объектов в конкретном приложении.

Если время задержки по-прежнему нас не устраивает — можно перейти к concurrent garbage collector, включив опцию -XX:+UseConcMarkSweepGC — алгоритм, который будет пытаться выполнять основную работу по маркировке объектов на удаление в отдельном потоке параллельно потокам приложения.

Настройка Concurrent garbage collector

ConcMarkSweep GC требует более внимательной настройки, — одной из основных целей является уменьшение количества stop-the-world пауз при отсутствии достаточного места в old generation для расположения объектов — т.к. эта фаза занимает в среднем больше времени, чем фаза полной сборки мусора при throughput GC. Как результат — может увеличиться длительность худшего случая сборки мусора, необходимо избегать частых переполнений old generation. Как правило, — при переходе на ConcMarkSweep GC рекомендуют увеличить размер old generation на 20-30% — запустим jvm с параметрами:


Общая пауза сократилась до 1,923 с.

Корректировка размера survivor

Снизу под графиком вы видите распределение объёма памяти приложения по числу переходов между стадиями Eden, Survivor1 и Survivor2 перед тем как они попадут в Old Generation. Дело в том, что один из способов уменьшения числа переполнений old generation в ConcMarkSweep GC — предотвратить прямое перетекание объектов из young generation напрямую в old — минуя survivor области.

Для слежения за распределением объектов по этапам можно запустить jvm с параметром -XX:+PrintTenuringDistribution.
В gc.log можем наблюдать:

Общее размер survivor объектов — 40900584, CMS по умолчанию использует 50% барьер заполненности области survivor. Таким образом получаем размер области

80 Мб. При запуске jvm он задаётся параметром -XX:SurvivorRatio, который определяется из формулы:

Желая оставить размер eden space тем же — получаем:



Распределение стало лучше, но общее время сильно не изменилось в силу специфики приложения, дело в том, что после частых малых сборок мусора размер выживших объектов всегда больше, чем доступный размер областей survivor, поэтому в нашем случае мы можем пожертвовать правильным распределением в угоду размера eden space:

2. Устройство сборщика мусора by Maoni Stephens (@maoni0)

Архитектура компонентов

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

Есть и другие способы вызвать сборщик, например вручную, с помощью GC.Collect. Также поток финализатора может получить асинхронное уведомление о том, что память заканчивается (что вызовет сборщик).

Устройство распределителя

Распределитель вызывается вспомогательными компонентами среды выполнения с указанием следующей информации:

  • необходимый размер выделяемого участка;
  • контекст выделения памяти для потока исполнения;
  • флаги, которые указывают, например, является ли объект финализируемым.

Сборщик мусора не предусматривает специальных методов обработки для разных типов объектов. Он получает информацию о размере объекта от среды выполнения.

В зависимости от размера, сборщик делит объекты на две категории: маленькие (< 85 000 байт) и большие (>= 85 000 байт). В целом сборка маленьких и больших объектов может происходить одинаково. Однако сборщик разделяет их по размеру, поскольку сжатие больших объектов требует много ресурсов.

Сборщик мусора выделяет память распределителю с учётом контекстов выделения. Размер контекста выделения определяется блоками выделяемой памяти.

Контексты выделения – небольшие области определённого сегмента кучи, каждая из которых предназначена для определённого потока исполнения. На машине с одним процессором (имеется в виду 1 логический процессор) используется один контекст выделения памяти для объектов поколения 0.

Блок выделяемой памяти – объём памяти, выделяемый распределителем каждый раз, когда ему требуется больше памяти, чтобы расположить объект внутри области. Размер блока обычно составляет 8 Кб, а средний размер управляемых объектов – 35 байт. Поэтому в одном блоке можно расположить множество объектов.

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

Распределитель устроен таким образом, чтобы:

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

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

эффективно использовать кэш: распределитель выделяет память блоками, а не для каждого объекта. Он обнуляет так много памяти, чтобы подготовить кэш процессора, поскольку некоторые объекты будут размещены прямо в нём. Блок выделяемой памяти обычно равен 8 Кб.

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

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

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

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

Устройство сборщика

Задачи сборщика мусора

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

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

Сборщик мусора CLR собирает объекты, логически разделённые по поколениям. После сборки объектов в поколении N, оставшиеся объекты маркируются как принадлежащие поколению N+1. Этот процесс называется продвижение объектов по поколениям. В этом процессе существуют исключения, когда необходимо перевести объект в поколение ниже или не продвигать его вообще.

В случае маленьких объектов куча делится на три поколения: gen0, gen1 и gen2. Для больших объектов существует только одно поколение – gen3. Gen0 и gen1 называют эфемерными поколениями (объекты живут в них короткое время).

Для кучи небольших объектов номер поколения означает их возраст. Например, gen0 – самое молодое поколение. Это не значит, что все объекты в gen0 моложе объектов в gen1 или gen2. Здесь существуют исключения, которые описаны ниже. Сборка поколения означает сборку объектов в этом поколении, а также во всех его более молодых поколениях.

Теоретически сборка больших и маленьких объектов может происходить одинаково. Однако поскольку сжатие крупных объектов требует много ресурсов, их сборка происходит по-другому. Большие объекты содержатся только в gen2 и собираются только во время сборки мусора в этом поколении из соображений производительности. Как gen2, так и gen3 могут быть большими, а сборка объекта в эфемерных поколениях (gen0 и gen1) не должна быть слишком ресурсозатратной.

Объекты размещаются в самом младшем поколении. Для маленьких объектов это gen0, а для больших – gen3.

Физическое описание управляемой кучи

Управляемая куча состоит из набора сегментов. Сегмент – непрерывный блок памяти, который ОС передаёт сборщику мусора. Сегменты кучи разбиваются на мелкие и большие участки для размещения маленьких и больших объектов. Сегменты каждой кучи связаны вместе. По крайней мере один сегмент для маленького объекта и один для большого резервируются при загрузке CLR.

В каждой куче маленьких объектов есть только один эфемерный сегмент, где находятся поколения gen0 и gen1. Этот сегмент может содержать или не содержать объекты поколения gen2. Кроме эфемерных сегментов, могут существовать один или более дополнительных сегментов, которые будут сегментами gen2, так как они содержат объекты поколения 2.

Куча больших объектов состоит из одного или более сегментов.

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

Сегменты кучи выделяются по мере необходимости. Если они не содержат используемых объектов, сегменты удаляются. Однако начальный сегмент в куче всегда существует. За один раз для каждой кучи выделяется один сегмент. В случае маленьких объектов это происходит во время сборки мусора, а для больших – во время выделения памяти для них. Такая схема повышает производительность, поскольку большие объекты собираются только в поколении 2 (что требует много ресурсов).

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

Пороговое значение объёма выделенной памяти

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

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

Выбор поколения для сборки мусора

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

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

Процесс сборки мусора

Этап маркировки

Во время этого этапа CLR должна найти все живые объекты.

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

Чтобы пометить старые объекты, ссылающиеся на новые, сборщик мусора использует специальные биты. Биты устанавливаются механизмом JIT-компилятора во время операций присвоения. Если объект принадлежит к эфемерному поколению, JIT-компилятор установит байт, содержащий бит, указывающий на исходное положение. Собирая мусор в эфемерных поколениях, сборщик может использовать эти биты для всей оставшейся кучи и просмотреть только те объекты, которым эти биты соответствуют.

Этап планирования

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

Этап перемещения

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

Этап сжатия

Этот этап достаточно прост, поскольку сборщик уже определил новые адреса для перемещения объектов во время этапа планирования. При сжатии объекты будут скопированы по этим адресам.

Этап уборки

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

Code flow

  • WKS GC: Сборка мусора в режиме рабочей станции
  • SVR GC: Сборка мусора в режиме сервера
Функциональное поведение
WKS GC без параллельной сборки мусора
  1. Пользовательский поток использовал всю выделенную для него память и вызывает сборщик мусора.
  2. Сборщик вызывает SuspendEE , чтобы приостановить все управляемые потоки.
  3. Сборщик выбирает поколение для очистки.
  4. Начинается маркировка объектов.
  5. Сборщик переходит на этап планирования и определяет необходимость сжатия.
  6. При необходимости сборщик перемещает объекты и выполняет сжатие. В другом случае – просто выполняет уборку.
  7. Сборщик вызывает RestartEE , чтобы вновь запустить управляемые потоки.
  8. Пользовательские потоки продолжают работу.
WKS GC с параллельной сборкой мусора

Этот алгоритм описывает фоновую сборку мусора.

  1. Пользовательский поток использовал всю выделенную для него память и вызывает сборщик мусора.
  2. Сборщик вызывает SuspendEE , чтобы приостановить все управляемые потоки.
  3. Сборщик определяет, нужно ли запустить фоновую сборку мусора.
  4. Если да, активируется поток фоновой сборки мусора. Этот поток вызывает RestartEE , чтобы возобновить управляемые потоки.
  5. Выделение памяти для управляемых процессов продолжается одновременно с фоновой сборкой мусора.
  6. Пользовательский поток может использовать всю выделенную для него память и запустит эфемерную сборку мусора (также известную как высокоприоритетная сборка мусора). Она выполняется так же, как в режиме рабочей станции без параллельной сборки мусора..
  7. Поток фоновой сборки мусора снова вызывает SuspendEE , чтобы завершить маркировку, а затем вызывает RestartEE , чтобы запустить параллельную уборку с работающими пользовательскими потоками.
  8. Фоновая сборка мусора завершается.
SVR GC без параллельной сборки мусора
  1. Пользовательский поток использовал всю выделенную для него память и вызывает сборщик мусора.
  2. Потоки сборки мусора в режиме сервера активируются и вызывают SuspendEE , чтобы приостановить выполнение управляемых потоков.
  3. Потоки сборки мусора в режиме сервера выполняют те же операции, что и в режиме рабочей станции без параллельной сборки мусора.
  4. Потоки сборки мусора в режиме сервера вызывают RestartEE , чтобы запустить управляемые потоки.
  5. Пользовательские потоки продолжают работу.
SVR GC с параллельной сборкой мусора

Алгоритм такой же, как и в случае с параллельной сборкой мусора в режиме рабочей станции, только нефоновая сборка выполняется в серверных потоках.

Физическая архитектура

Эта секция поможет понять code flow.

Когда у пользовательского потока заканчивается память, он может получить свободное пространство с помощью функции try_allocate_more_space .

Функция try_allocate_more_space вызывает GarbageCollectGeneration , когда нужно запустить сборщик мусора.

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

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

Garbage Collector. Полный курс + перевод из BOTR

В данной статье вы встретите сразу два источника информации:



Основные принципы настройки Garbage Collection с нуля

Количественные характеристики оценки эффективности GC

Рассмотрим следующие показатели:

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

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

Основные принципы настройки GC
  • Необходимо стремиться к тому, чтобы максимальное количество объектов очищалось при работе малого GC(minor grabage collection). Этот принцип позволяет уменьшить число и частоту работы полного сборщика мусора(full garbage collection) — чья работа является основной причиной больших задержек в приложении
  • Чем больше памяти выделено приложению, тем лучше работает сборка мусора и тем лучше достигаются количественные характеристики по пропускной способности и времени отклика
  • Эффективно настроить можно только 2 из 3 количественных характеристик — пропускная способность, время отклика, размер выделенной памяти — под эффективным значением размера необходимой памяти понимается её минимизация

Рассмотрим пример простого приложения(которое, к примеру, может эмулировать работу вэб-приложения, в ходе которого идёт обращение к БД и накопление возвращаемого результат), в котором в несколько потоков идёт обращение к методу makeObjects(), в ходе которого в цикле непрерывно формируется объект, занимающий определённый объём в куче, затем с ним происходят какие-либо вычисления — делается задержка, ссылка на объект при этом не утекает из метода и по его завершению GC может понять, что данный объект подлежит очистке.

Запускать эксперимент мы будем на:

Для которого по умолчанию включен режим — server и UseParallelGC(многопоточная работа фазы малой сборки мусора)

Для оценки общей величины паузы сборщика мусора можно запускать в режиме:

И суммировать задержку по логу gc.log:

Где real=0.01 secs — реальное время, затраченное на сборку.

А можно воспользоваться утилитой VisualVm, с установленным плагином VisualGC, в котором наглядно можно наблюдать распределение памяти по различным областям GC(Eden, Survivor1, Survivor2, Old) и видеть статистику по запуску и длительности сборки мусора.

Определение размера необходимой памяти

Для начала мы должны запустить приложение с возможно большим размером памяти, чем это это реально необходимо приложению. Если мы не знаем изначально, сколько будет занимать наше приложение в памяти — можно запустить приложение без указания -Xmx и -Xms и HotSpot VM сама выберет размер памяти. Если при старте приложения мы получим OutOfMemory(Java heap space или PermGen space), то мы можем итеративно увеличивать размер доступной памяти(-Xmx или -XX:PermSize) до тех пор пока ошибки не уйдут.
Следующим шагом будет вычисление размера долго-живущих живых данных — это размер old и permanent областей кучи после фазы полной сборки мусора. Этот размер — примерный объём памяти, необходимый для функционирования приложения, для его получения можно посмотреть на размер областей после серии полной сборки. Как правило размер необходимой памяти для приложения -Xms и -Xmx в 3-4 раза больше, чем объём живых данных. Так, для лога, указанного выше — величина old области после фазы полной сборки мусора — 349363K. Тогда предлагаемое значение -Xmx и -Xms

1400 Мб. -XX:PermSize and -XX:MaxPermSize — в 1.5 раз больше, чем PermGenSize после фазы полной сборки мусора — 13324K

20 Мб. Размер young generation принимаю равным 1-1.5 размера объёма живых данных

525 Мб. Тогда получаем строку запуска jvm с такими параметрами:

В VisualVm получаем такую картину:


Всего за 30 сек эксперимента было произведено 54 сборки — 31 малых и 23 полных — с общим временем остановки 3,227c. Данная величина задержки может не удовлетворять необходимым требованиям — посмотрим, сможем ли мы улучшить ситуацию без изменения кода приложения.

Настройка допустимого времени отклика
  • Измерение длительности малой сборки мусора
  • Измерение частоты малой сборки мусора
  • Измерение длительности худшего случая полной сборки мусора
  • Измерение частоты худшего случая полной сборки мусора
Корректировка размера young и old generation

Время, необходимое для осуществления фазы малой сборки мусора, напрямую зависит от числа объектов в young generation, чем меньше его размер — тем меньше длительность, но при этом возрастает частота, т.к. область начинает чаще заполняться. Попробуем уменьшить время каждой малой сборки, уменьшив размер young generation, сохранив при этом размер old generation. Примерно можно оценить, что каждую секунду мы должны очищать в young generation 50потоков*8объектов*1Мб

400Мб. Запустим с параметрами:

В VisualVm получаем такую картину:


На общее время работы малой сборки мусора мы повлиять не смогли — 1,533с — увеличилась частота малых сборок, но общее время ухудшилось — 3,661 из-за того, что увеличилась скорость заполнения old generation и увеличилась частота вызова полной сборки мусора. Чтобы побороть это — попробуем увеличить размер old generation — запустим jvm с параметрами:


Общая пауза теперь улучшилась и составляет 2,637 с а общее значение необходимой для приложения памяти при этом уменьшилось — таким образом итеративно можно найти правильный баланс между old и young generation для распределения времени жизни объектов в конкретном приложении.

Если время задержки по-прежнему нас не устраивает — можно перейти к concurrent garbage collector, включив опцию -XX:+UseConcMarkSweepGC — алгоритм, который будет пытаться выполнять основную работу по маркировке объектов на удаление в отдельном потоке параллельно потокам приложения.

Настройка Concurrent garbage collector

ConcMarkSweep GC требует более внимательной настройки, — одной из основных целей является уменьшение количества stop-the-world пауз при отсутствии достаточного места в old generation для расположения объектов — т.к. эта фаза занимает в среднем больше времени, чем фаза полной сборки мусора при throughput GC. Как результат — может увеличиться длительность худшего случая сборки мусора, необходимо избегать частых переполнений old generation. Как правило, — при переходе на ConcMarkSweep GC рекомендуют увеличить размер old generation на 20-30% — запустим jvm с параметрами:


Общая пауза сократилась до 1,923 с.

Корректировка размера survivor

Снизу под графиком вы видите распределение объёма памяти приложения по числу переходов между стадиями Eden, Survivor1 и Survivor2 перед тем как они попадут в Old Generation. Дело в том, что один из способов уменьшения числа переполнений old generation в ConcMarkSweep GC — предотвратить прямое перетекание объектов из young generation напрямую в old — минуя survivor области.

Для слежения за распределением объектов по этапам можно запустить jvm с параметром -XX:+PrintTenuringDistribution.
В gc.log можем наблюдать:

Общее размер survivor объектов — 40900584, CMS по умолчанию использует 50% барьер заполненности области survivor. Таким образом получаем размер области

80 Мб. При запуске jvm он задаётся параметром -XX:SurvivorRatio, который определяется из формулы:

Желая оставить размер eden space тем же — получаем:



Распределение стало лучше, но общее время сильно не изменилось в силу специфики приложения, дело в том, что после частых малых сборок мусора размер выживших объектов всегда больше, чем доступный размер областей survivor, поэтому в нашем случае мы можем пожертвовать правильным распределением в угоду размера eden space:

Garbage collector ratiborus как пользоваться


И комп при этом начинает жить своей насыщенной adware жизнью.
В ярлыки всех браузеров добавляется к пути к экзешнику браузера ссылка на рекламный сайт, устанавливаются всякие мусорные программы типа PC Booster, SystemAssister и прочей лажи. А так же появление назойливой блевотной рекламы во всех браузерах и переадресации куда угодно, только не туда, куда надо!

Решить проблему удалось с помощью ПО от Emsisoft

Найти и скачать бесплатную версию Emsisoft Emergency Kit можно по ссылке указанной в цитате:

Читайте также: