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

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

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

1. Введение

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

2. Понимание проблем при сборке мусора

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

2.1. Соображения для сборщика мусора

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

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

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

2.2. Существующие сборщики мусора в Java

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

./926467aab62378c1f77c1fe0ae15d599.jpg

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

Коллектор Concurrent Mark Sweep (CMS), представленный в Java 1.4, представляет собой параллельный коллектор поколений с малой паузой. Он работает с копированием в молодом поколении и маркировкой в старом поколении:

./164082de60dc9a68c73caaa2f4da3971.jpg

Он пытается минимизировать время паузы, выполняя большую часть работы одновременно с пользовательской программой. Тем не менее, он по-прежнему имеет проблемы, приводящие к непредсказуемым паузам , требует больше процессорного времени и не подходит для кучи размером более 4 ГБ.

В качестве долгосрочной замены CMS в Java 7 был представлен сборщик Garbage First (G1) . G1 — это сборщик с поколенческим, параллельным, параллельным и инкрементально уплотняемым сборщиком с малой паузой. Работает с копированием в молодом поколении и маркировкой-компактом в старом поколении:

./5a627a3bdefe6773114e931b2269d71e.jpg

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

Итак, гонка за лучшим сборщиком мусора продолжается, особенно тот, который еще больше сокращает время паузы. В последнее время JVM представила ряд экспериментальных сборщиков, таких как Z, Epsilon и Shenandoah. Кроме того, G1 продолжает улучшаться.

На самом деле цель состоит в том, чтобы максимально приблизиться к Java без пауз!

3. Сборщик мусора Шенандоа

Shenandoahэкспериментальный сборщик, который был представлен в Java 12 и позиционируется как специалист по задержкам . Он пытается сократить время пауз, выполняя больше работы по сборке мусора одновременно с пользовательской программой.

Например, Shenendoah пытается одновременно перемещать и сжимать объекты. По сути это означает, что время паузы в Shenandoah больше не прямо пропорционально размеру кучи. Следовательно, он может обеспечить последовательное поведение с низкой паузой, независимо от размера кучи .

3.1. Структура кучи

Shenandoah, как и G1, является региональным коллекционером. Это означает, что он делит область кучи на набор областей одинакового размера . Регион — это, по сути, единица выделения или освобождения памяти:

./7127f37b15dd91e04871f2d113bd912d.jpg

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

3.2. Макет объекта

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

./80ec7f86a5853e4c298db57a229eff06.jpg

Шенандоа добавляет дополнительное слово к этому макету объекта . Это служит косвенным указателем и позволяет Shenandoah перемещать объекты без обновления всех ссылок на них. Это также известно как указатель Брукса .

3.3. Барьеры

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

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

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

./c32bcefe859aba43839d8444b7a43776.jpg

Shenandoah использует несколько барьеров на разных этапах, таких как барьер SATB, барьер чтения и барьер записи . Мы увидим, где они используются в следующих разделах.

3.4. Режимы, эвристики и режимы отказов

Режимы определяют то, как работает Шенандоа , например, какие барьеры он использует, а также определяют его рабочие характеристики. Доступны три режима: нормальный/SATB, iu и пассивный. Режим нормальный/SATB используется по умолчанию.

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

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

4. Этапы коллекции Шенандоа

Цикл сбора Шенандоа состоит в основном из трех этапов: пометка, эвакуация и обновление ссылок. Хотя большая часть работы на этих этапах выполняется одновременно с пользовательской программой, все же есть небольшие части, которые должны выполняться в режиме остановки мира.

4.1. Маркировка

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

./41f55dd11a1ecf03456ac8bee05ab403.jpg

Маркировка в режиме остановки мира проще, но в параллельном режиме она усложняется. Это связано с тем, что программа пользователя одновременно изменяет граф объекта во время выполнения маркировки. Shenandoah решает эту проблему с помощью алгоритма Snapshot At the Beginning (SATB) .

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

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

4.2. Уборка и эвакуация

После того, как маркировка завершена, области мусора готовы к утилизации. Мусорные области — это области, в которых нет живых объектов . Очистка происходит одновременно.

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

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

./814eefe51af65029674e522cbacb010c.jpg

Кроме того, Shenandoah использует барьеры чтения и записи, чтобы гарантировать, что во время одновременной эвакуации поддерживается строгий инвариант «к пространству» . Это означает, что чтение и запись должны происходить из пространства-то, которое гарантированно выдержит эвакуацию.

4.3. Справочное обновление

Эта фаза в цикле сбора заключается в обходе кучи и обновлении ссылок на объекты, которые были перемещены во время эвакуации :

./0f21178facbe6b4c996fc72da8afd17b.jpg

Справочная фаза обновления, опять же, в основном выполняется одновременно . Существуют короткие периоды init-update-refs, которые инициализируют фазу ссылки на обновление, и final-update-refs, которые повторно обновляют корневой набор и перезапускают регионы из набора сбора. Только для этого требуется режим остановки мира.

5. Сравнение с другими экспериментальными коллекторами

Shenandoah — не единственный экспериментальный сборщик мусора, недавно представленный в Java. Другие включают Z и Эпсилон. Давайте поймем, как они сравниваются с Шенандоа.

5.1. Z коллектор

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

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

Вообще говоря, цели коллекционера Z аналогичны целям Шенандоа . Оба они нацелены на достижение малого времени паузы, которое не прямо пропорционально размеру кучи. Тем не менее, с Shenandoah доступно больше вариантов настройки, чем с Z-коллектором .

5.2. Коллекционер Эпсилон

Epsilon , также представленный в Java 11, имеет совершенно другой подход к сборке мусора. По сути, это пассивный или «нерабочий» сборщик, что означает, что он обрабатывает выделение памяти, но не перерабатывает ее! Итак, когда в куче заканчивается память, JVM просто выключается.

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

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

Очевидно, что у Эпсилон совсем другие цели, чем у Шенандоа .

6. Заключение

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

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