Перейти к основному содержимому

Выбор алгоритма GC в Java

· 7 мин. чтения

1. Введение

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

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

В этой статье мы попытаемся ответить на этот вопрос.

2. Что такое GC?

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

В молодом (Эдемском) поколении размещаются вновь созданные объекты . Обычно он небольшой (100-500 МБ) и также имеет два оставшихся места . Старое поколение — это место, где хранятся старые или устаревшие объекты `` — обычно это долгоживущие объекты. Это пространство гораздо больше, чем молодое поколение.

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

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

3. Переменные, которые следует учитывать

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

3.1. Размер кучи

Это общий объем рабочей памяти, выделенной ОС для JVM. Теоретически, чем больше объем памяти, тем больше объектов можно хранить до сбора, что приводит к увеличению времени сборки мусора . Минимальный и максимальный размеры кучи можно задать с помощью параметров командной строки -Xms=<n> и -Xmx=<m> .

3.2. Размер набора данных приложения

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

3.3. Количество процессоров

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

3.4. Время паузы

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

3.5. пропускная способность

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

3.6. Объем памяти

Это рабочая память, используемая процессом GC. Когда установка имеет ограниченную память или много процессов, эта переменная может определять масштабируемость.

3.7. Оперативность

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

3.8. Java-версия

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

3.9. Задержка

Это отзывчивость приложения. Пауза GC напрямую влияет на эту переменную.

4. Сборщики мусора

Помимо последовательного GC, все остальные сборщики наиболее эффективны, когда доступно более одного ядра:

4.1. Серийный GC

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

Плюсы:

  • Без накладных расходов на межпотоковое взаимодействие это относительно эффективно.
  • Он подходит для машин клиентского класса и встроенных систем.
  • Он подходит для приложений с небольшими наборами данных.
  • Даже на многопроцессорном оборудовании, если наборы данных малы (до 100 МБ), это все равно может быть наиболее эффективным.

Минусы:

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

4.2. Параллельный/пропускной GC

Этот сборщик использует несколько потоков для ускорения сборки мусора . В Java версии 8 и более ранних версиях это значение по умолчанию для машин серверного класса. Мы можем переопределить это значение по умолчанию, используя параметр -XX:+UseParallelGC .

Плюсы:

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

Минусы:

  • Приложения подвергаются длительным паузам во время операций Stop-the-world .
  • Он плохо масштабируется с размером кучи.

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

4.3. Параллельная очистка меток (CMS) GC

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

Мы можем использовать параметр -XX:+UseConcMarkSweepGC , чтобы включить сборщик CMS. Основная команда Java объявила его устаревшим, начиная с Java 9, и полностью удалила его в Java 14.

Плюсы:

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

Минусы:

  • Он устарел с Java 9 и удален в Java 14.
  • Он становится относительно неэффективным, когда наборы данных достигают гигантских размеров или при сборе огромных куч.
  • Это требует, чтобы приложение делилось ресурсами с GC во время параллельных фаз.
  • Могут быть проблемы с пропускной способностью, так как в целом больше времени тратится на операции GC.
  • В целом, он использует больше процессорного времени из-за своей преимущественно параллельной природы.

4.4. G1 (сначала мусор) GC

G1 использует несколько фоновых потоков GC для сканирования и очистки кучи, как и CMS. На самом деле основная команда Java разработала G1 как улучшение CMS, исправив некоторые из ее недостатков с помощью дополнительных стратегий.

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

Начиная с Java 9, G1 является коллектором по умолчанию для машин серверного класса. Мы можем явно включить его, указав -XX:+UseG1GC в командной строке.

Плюсы:

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

Минусы:

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

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

5. Вывод

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

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