Учебники

Виртуальная машина Java — настройка GC

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

Размер кучи

Размер кучи является важным фактором производительности наших Java-приложений. Если он слишком мал, он будет часто заполняться и, как следствие, GC должен будет часто собирать его. С другой стороны, если мы просто увеличим размер кучи, хотя ее нужно собирать реже, длина пауз увеличится.

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

Предположим, что у машины 8 ГБ памяти, а JVM видит 16 ГБ виртуальной памяти, JVM не знает, что на самом деле в системе доступно только 8 ГБ. Он просто запросит 16G у ОС и, как только получит эту память, продолжит ее использовать. Операционная система должна будет обмениваться большим количеством данных, и это является огромным снижением производительности системы.

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

Таким образом, здесь возникает вопрос о том, как мы должны определить оптимальный размер кучи. Первое правило — никогда не запрашивать у ОС больше памяти, чем есть на самом деле. Это полностью предотвратит проблему частой замены. Если на машине установлено и работает несколько JVM, то общий запрос памяти для всех из них меньше, чем фактический объем ОЗУ в системе.

Вы можете контролировать размер запроса памяти JVM, используя два флага:

  • -XmsN — контролирует начальную запрошенную память.

  • -XmxN — управляет максимальным объемом памяти, который может быть запрошен.

-XmsN — контролирует начальную запрошенную память.

-XmxN — управляет максимальным объемом памяти, который может быть запрошен.

Значения по умолчанию обоих этих флагов зависят от базовой ОС. Например, для 64-битных JVM, работающих на MacOS, -XmsN = 64M и -XmxN = минимум 1G или 1/4 от общей физической памяти.

Обратите внимание, что JVM может автоматически настраиваться между двумя значениями. Например, если он замечает, что происходит слишком много GC, он будет продолжать увеличивать объем памяти, пока он находится ниже -XmxN и желаемые цели производительности достигнуты.

Если вы точно знаете, сколько памяти требуется вашему приложению, вы можете установить -XmsN = -XmxN. В этом случае JVM не нужно вычислять «оптимальное» значение кучи, и, следовательно, процесс GC становится немного более эффективным.

Размеры поколения

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

Если размер YG очень большой, то он будет собираться реже. Это приведет к тому, что меньшее количество объектов будет переведено в OG. С другой стороны, если вы слишком сильно увеличите размер OG, то сбор и сжатие займет слишком много времени, и это приведет к длительным паузам STW. Таким образом, пользователь должен найти баланс между этими двумя значениями.

Ниже приведены флаги, которые вы можете использовать для установки этих значений —

  • -XX: NewRatio = N: отношение YG к OG (значение по умолчанию = 2)

  • -XX: NewSize = N: начальный размер YG

  • -XX: MaxNewSize = N: максимальный размер YG

  • -XmnN: установить NewSize и MaxNewSize на одно и то же значение, используя этот флаг

-XX: NewRatio = N: отношение YG к OG (значение по умолчанию = 2)

-XX: NewSize = N: начальный размер YG

-XX: MaxNewSize = N: максимальный размер YG

-XmnN: установить NewSize и MaxNewSize на одно и то же значение, используя этот флаг

Первоначальный размер YG определяется значением NewRatio по заданной формуле —

 (общий размер кучи) / (newRatio + 1)

Поскольку начальное значение newRatio равно 2, в приведенной выше формуле начальное значение YG составляет 1/3 от общего размера кучи. Вы всегда можете переопределить это значение, явно указав размер YG с помощью флага NewSize. Этот флаг не имеет никакого значения по умолчанию, и если он не установлен явно, размер YG будет рассчитываться по формуле выше.

Permagen и Metaspace

Permagen и metaspace являются областями кучи, где JVM хранит метаданные классов. Пространство называется «permagen» в Java 7, а в Java 8 оно называется «metaspace». Эта информация используется компилятором и средой выполнения.

Вы можете контролировать размер пермагена, используя следующие флаги: -XX: PermSize = N и -XX: MaxPermSize = N. Размер Metaspace можно контролировать с помощью: -XX: Metaspace- Size = N и -XX: MaxMetaspaceSize = N.

Существуют некоторые различия в управлении permagen и metaspace, когда значения флага не установлены. По умолчанию оба имеют начальный размер по умолчанию. Но хотя метапространство может занимать столько кучи, сколько необходимо, permagen может занимать не более начальных значений по умолчанию. Например, 64-битная виртуальная машина Java имеет 82M пространства кучи в качестве максимального размера пермагена.

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

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