Книга «Эффективный Java. Тюнинг кода на Java 8, 11 и дальше. 2-е межд. издание»

0 32

Книга «Эффективный Java. Тюнинг кода на Java 8, 11 и дальше. 2-е межд. издание»

Вы сможете разобраться в производительности приложений Java в контексте как JVM, так и платформы Java, освоите средства, функции и процессы, которые могут повысить производительность в LTS-версиях Java, и познакомитесь с новыми возможностями (такими как предварительная компиляция и экспериментальные уборщики мусора). В этой книге вы: — Узнаете, как платформы и компиляторы Java влияют на производительность. — Разберетесь c механизмом уборки мусора. — Освоите четыре принципа получения наилучших результатов при тестировании производительности. — Научитесь пользоваться JDK и другими инструментами оценки производительности. — Узнаете как настройка и приемы программирования позволяют минимизировать последствия уборки мусора. — Научитесь решать проблемы производительности средствами Java API. — Поймете, как улучшить производительность приложений баз данных Java.

Алгоритмы уборки мусора

OpenJDK 12 предоставляет различные алгоритмы уборки мусора с разной степенью поддержки в более ранних версиях. В табл. 5.1 перечислены эти алгоритмы с указанием их статуса в выпусках OpenJDK и Oracle.

Книга «Эффективный Java. Тюнинг кода на Java 8, 11 и дальше. 2-е межд. издание»

S — полная поддержка; D — считается устаревшим; E — экспериментальная поддержка; E2 — экспериментальная поддержка в сборках OpenJDK, но не в сборках Oracle.

Ниже приводятся краткие описания всех алгоритмов; в главе 6 приведена более подробная информация об их настройке.

Последовательный уборщик мусора

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

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

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

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

В JDK 8 параллельный уборщик мусора используется по умолчанию на всех 64-разрядных машинах с двумя и более процессорами. Параллельный уборщик мусора использует несколько потоков для уничтожения молодого поколения, вследствие чего малая уборка мусора работает намного быстрее при использовании последовательного уборщика мусора. Алгоритм также использует несколько потоков для обработки старого поколения — собственно, именно по этой причине он называется параллельным.

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

Учтите, что в старых версиях JVM параллельная уборка мусора включалась для старых и новых поколений по отдельности, поэтому иногда встречаются упоминания флага -XX:+UseParallelOldGC. Этот флаг считается устаревшим (хотя и продолжает функционировать, и его можно отключить для параллельной уборки только молодого поколения, если по какой-то причине возникнет такая необходимость).

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

Уборщик мусора G1 использует стратегию конкурентной уборки мусора для очистки кучи с минимальными паузами. Этот уборщик используется по умолчанию в JDK 11 и более поздних версиях для 64-разрядных JVM на машинах, оснащенных двумя и более процессорами.
Уборщик мусора G1 делит кучу на области, но при этом рассматривает кучу как разделенную на два поколения. Некоторые области формируют молодое поколение, при уборке которого приостанавливаются все потоки приложения, а все живые объекты перемещаются в старое поколение или области выживших объектов (для чего используются множественные потоки).
В уборщике мусора G1 старое поколение обрабатывается фоновыми потоками, которым не нужно останавливать потоки приложения для выполнения большей части своей работы. Так как старое поколение делится на области, уборщик мусора G1 может освобождать объекты из старого поколения, копируя их из одной области в другую; это означает, что он (по крайней мере частично) сжимает кучу в ходе нормальной обработки. Это способствует предотвращению фрагментации куч G1, хотя полностью исключить ее нельзя.

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

Уборщик мусора G1 включается при помощи флага -XX:+UseG1GC. В большинстве случаев он используется по умолчанию в JDK 11, но также сохраняет функциональность в JDK 8 — особенно в поздних сборках JDK 8, содержащих многие важные исправления ошибок и улучшения производительности, которые были перенесены из более поздних выпусков. Как можно увидеть при углубленном анализе уборщика G1, в JDK 8 не поддерживается один важный аспект производительности, из-за которого данный механизм может стать непригодным для этого выпуска.

Уборщик мусора CMS

Уборщик мусора CMS стал первым конкурентным уборщиком. Как и другие алгоритмы, CMS останавливает все потоки приложений в ходе малой уборки мусора, которую он проводит в нескольких потоках.

CMS официально считается устаревшим в JDK 11 и выше, а использовать его в JDK 8 не рекомендуется. С практической точки зрения главный недостаток CMS заключается в том, что он не может сжимать кучу в ходе фоновой обработки. Если куча фрагментируется (что с большой вероятностью произойдет в какой-то момент), CMS приходится остановить все потоки приложения и сжать кучу, что противоречит самой цели конкурентного уборщика. С учетом этого обстоятельства и появлением уборщика G1 использовать CMS более не рекомендуется.
CMS включается флагом -XX:+UseConcMarkSweepGC, который по умолчанию равен false. Традиционно CMS также требовал установки флага -XX:+UseParNewGC (в противном случае уборка в молодом поколении будет выполняться одним потоком), хотя этот флаг считается устаревшим.

Экспериментальные уборщики мусора

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

Включение и выключение принудительной уборки мусора
Уборка мусора обычно происходит тогда, когда JVM считает, что она необходима: малая уборка мусора инициируется при заполнении нового поколения, полная — при заполнении старого поколения, а конкурентная (если она применима) — когда куча близка к заполнению.

Java предоставляет механизм, при помощи которого приложения могут инициировать принудительную уборку мусора: метод System.gc(). Вызывать этот метод почти всегда нежелательно. Вызов всегда запускает полную уборку мусора (даже если JVM работает с уборщиком G1 или CMS), поэтому потоки приложения будут остановлены на относительно длительный период времени. Вызов этого метода не сделает приложение более эффективным; он заставит уборку мусора выполниться ранее, чем она могла бы произойти, но в действительности проблема не исчезает, а всего лишь перемещается в другое место.

У каждого правила есть исключения, особенно при мониторинге производительности или хронометражных тестах. Для небольших хронометражных тестов, которые выполняют код для разогрева JVM, выполнение принудительной уборки мусора перед циклом измерений может иметь смысл. (Так поступает jmh, хотя обычно это не обязательно.) Аналогичным образом, при анализе кучи обычно стоит провести полную уборку мусора перед получением дампа. Многие способы получения дампа кучи все равно проводят полную уборку мусора, но ее также можно инициировать другими способами: выполнить команду jcmd<идентификатор_процесса> GC.run или подключиться к JVM при помощи jconsole и щелкнуть на кнопке Perform GC на панели Memory.

Другим исключением является механизм RMI (Remote Method Invocation), который вызывает System.gc() каждый час в процессе работы распределенного уборщика мусора. Периодичность вызова можно изменить, присвоив другие значения двум системным свойствам: -Dsun.rmi.dgc.server.gcInterval=N и -Dsun.rmi.dgc.client.gcInterval=N. Значения N задаются в миллисекундах, а значение по умолчанию равно 3 600 000 (один час).

Если вы выполняете сторонний код, который вызывает метод System.gc(), а для вас это нежелательно, уборку мусора можно предотвратить включением флага -XX:+DisableExplicitGC в аргументы JVM; по умолчанию этот флаг равен false. Такие приложения, как серверы Java EE, часто включают этот аргумент, для того чтобы вызовы RMI GC не мешали их работе.

Резюме

— Поддерживаемые алгоритмы уборки мусора принимают разные меры для достижения своей цели — минимизации воздействия уборки мусора на приложение.

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

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

— Уборщик мусора G1 используется по умолчанию в JDK 11 и выше; он проводит конкурентную чистку старого поколения во время выполнения потоков приложения, теоретически избегая полной уборки мусора. Такая архитектура снижает вероятность полной уборки мусора по сравнению с CMS.

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

Выбор алгоритма уборки мусора

Выбор алгоритма уборки мусора зависит от нескольких факторов: от имеющегося оборудования, от специфики приложения, от целей приложения по производительности. В JDK 11 уборщик мусора G1 часто оказывается наилучшим вариантом; в JDK 8 выбор зависит от приложения.
Для начала будем условно считать, что алгоритм G1 является более подходящим вариантом, но у каждого правила есть свои исключения. В случае уборки мусора эти исключения связаны с объемом ресурсов процессора, необходимых приложению, относительно доступного оборудования и вычислительными ресурсами, необходимыми потокам G1 для фоновой обработки.

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

Источник

Оставьте ответ