Факторы, влияющие на производительность сборки мусора в Java

Два наиболее важных фактора, влияющих на производительность сборки мусора, - это общая доступная память и доля кучи (heap), выделенной молодому поколению.

Общая куча (heap)

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

Примечание. В следующем обсуждении, касающемся увеличения и сжатия кучи, макета кучи и значений по умолчанию, в качестве примера используется последовательный сборщик (serial collector). Хотя другие сборщики используют аналогичные механизмы, представленные здесь подробности могут не относиться к другим сборщикам.

Параметры кучи, влияющие на размер поколения

Ряд параметров влияет на размер поколения. Рисунок ниже иллюстрирует разницу между выделенным пространством и виртуальным пространством в куче. При инициализации виртуальной машины все пространство для кучи резервируется. Размер зарезервированного пространства можно указать с помощью опции -Xmx. Если значение параметра -Xms меньше значения параметра -Xmx, то не все зарезервированное пространство немедленно выделяется виртуальной машине. На этой фигуре незафиксированное пространство обозначено как "виртуальное" (virtual). Различные части кучи, то есть старое поколение и молодое поколение, могут расти до предела виртуального пространства по мере необходимости.

Некоторые параметры являются отношениями одной части кучи к другой. Например, параметр -XX:NewRatio обозначает относительный размер старого поколения к молодому поколению.

Значения параметров по умолчанию для размера кучи

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

Этот целевой диапазон задается в процентах с помощью параметров -XX:MinHeapFreeRatio=<minimum> и -XX:MaxHeapFreeRatio=<maximum>, а общий размер ограничен снизу -Xms<min> и сверху -Xmx<max>.

С этими параметрами, если процент свободного пространства в поколении падает ниже 40%, тогда поколение расширяется, чтобы сохранить 40% свободного пространства, вплоть до максимально допустимого размера поколения. Точно так же, если свободное пространство превышает 70%, тогда поколение сокращается, так что только 70% пространства является свободным, при условии минимального размера поколения.

Вычисления, используемые в Java SE для сборщика Parallel, теперь используются для всех сборщиков мусора. Частью расчета является верхний предел максимального размера кучи для 64-битных платформ. Существует аналогичный расчет для клиентской JVM, который приводит к меньшим максимальным размерам кучи, чем для серверной JVM.

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

  • Если у вас нет проблем с паузами, попробуйте выделить виртуальной машине как можно больше памяти. Размер по умолчанию часто слишком мал.
  • Установка -Xms и -Xmx в одно и то же значение повышает предсказуемость, удаляя самое важное решение по размеру с виртуальной машины. Однако виртуальная машина не сможет компенсировать это, если вы сделаете плохой выбор.
  • Как правило, увеличивайте объем памяти по мере увеличения числа процессоров, поскольку распределение может выполняться параллельно.

Сохранение динамической площади (Dynamic Footprint) путем минимизации размера кучи Java

Если вам нужно минимизировать объем динамической памяти (максимальный объем оперативной памяти, потребляемой во время выполнения) для вашего приложения, то вы можете сделать это, минимизировав размер кучи Java. Приложения Java SE Embedded могут требовать этого.

Минимизируйте размер кучи Java, уменьшив значения параметров -XX:MaxHeapFreeRatio (значение по умолчанию - 70%) и -XX:MinHeapFreeRatio (значение по умолчанию - 40%) с параметрами командной строки -XX:MaxHeapFreeRatio и -XX:MinHeapFreeRatio. Понижение -XX:MaxHeapFreeRatio до 10% и -XX:MinHeapFreeRatio показало успешное уменьшение размера кучи без чрезмерного снижения производительности; однако результаты могут сильно различаться в зависимости от вашего приложения. Попробуйте разные значения этих параметров, пока они не станут настолько низкими, насколько это возможно, но при этом сохранят приемлемую производительность.

Кроме того, вы можете указать -XX:-ShrinkHeapInSteps, что немедленно уменьшает кучу Java до целевого размера (указанного параметром -XX:MaxHeapFreeRatio). С этим параметром вы можете столкнуться с ухудшением производительности. По умолчанию среда выполнения Java постепенно уменьшает кучу Java до целевого размера; этот процесс требует нескольких циклов сбора мусора.

Молодое поколение

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

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

Варианты размеров молодого поколения

По умолчанию размер молодого поколения контролируется параметром -XX:NewRatio.

Например, установка -XX:NewRatio=3 означает, что соотношение между молодым и старым поколением составляет 1:3. Другими словами, объединенный размер пространства eden и Survivor будет составлять одну четвертую от общего размера кучи.

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

Размер пространства Survivor (выжившие объекты)

Вы можете использовать опцию -XX:SurvivorRatio для настройки размера пространства для объектов оставшихся в живых, но часто это не важно для производительности.

Например, -XX:SurvivorRatio=6 устанавливает соотношение между eden и пространством оставшихся в живых равным 1:6. Другими словами, каждое пространство выживших будет одной шестой от размера eden, и, следовательно, одна восьмая размера молодого поколения (не одна седьмая, потому что есть два пространства выживших).

Если оставшиеся в живых пространства слишком малы, то копирующая коллекция переполняется непосредственно в старое поколение. Если места для выживших слишком велики, они бесполезно пусты. При каждой сборке мусора виртуальная машина выбирает пороговое число, которое представляет собой число копий объекта до его устаревания. Этот порог выбран таким образом, чтобы выжившие наполовину были полны. Вы можете использовать конфигурацию журнала -Xlog:gc,age можно использовать для отображения этого порога и возраста объектов в новом поколении. Это также полезно для наблюдения за распределением приложения в течение жизни.

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

Опция Значение по умолчанию
-XX:NewRatio 2
-XX:NewSize 1310 MB
-XX:MaxNewSize не ограничено
-XX:SurvivorRatio 8

Максимальный размер молодого поколения рассчитывается из максимального размера общей кучи и значения параметра -XX:NewRatio. "Не ограниченное" значение по умолчанию для параметра -XX:MaxNewSize означает, что вычисленное значение не ограничено -XX:MaxNewSize, если в командной строке не указано значение для -XX:MaxNewSize.

Ниже приведены общие рекомендации для серверных приложений:

  • Сначала определитесь с максимальным размером кучи, который вы можете позволить виртуальной машине предоставить. Затем нарисуйте свою метрику производительности относительно размеров молодого поколения, чтобы найти наилучшую настройку.
    • Обратите внимание, что максимальный размер кучи всегда должен быть меньше объема памяти, установленного на машине, чтобы избежать чрезмерных сбоев страниц и перебора.
  • Если общий размер кучи фиксирован, то увеличение размера молодого поколения требует уменьшения размера старого поколения. Держите старое поколение достаточно большим, чтобы в него можно было в любой момент хранить все оперативные данные, используемые приложением, а также некоторое свободное пространство (от 10 до 20% и более).
  • С учетом ранее заявленного ограничения на старое поколение:
    • Отдавайте молодому поколению много памяти.
    • Увеличивайте размер молодого поколения по мере увеличения числа процессоров, поскольку распределение может быть распараллелено.

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


Комментарии

Популярные сообщения из этого блога

Методы класса Object в Java

Как получить текущий timestamp в Java

Основные опции JVM для повышения производительности и отладки