JEP 376: ZGC: Concurrent Thread-Stack Processing
ZGC: конкурентная обработка стеков потоков
| Ответственный | Erik Österlund |
| Тип | Feature |
| Область | Implementation |
| Статус | Closed / Delivered |
| Выпуск | 16 |
| Компонент | hotspot / gc |
| Обсуждение | hotspot dash gc dash dev at openjdk dot java dot net |
| Трудоёмкость | S |
| Длительность | S |
| Рецензенты | Mikael Vidstedt, Per Liden, Stefan Karlsson |
| Одобрен | Mikael Vidstedt |
| Создан | 2020/02/21 09:12 |
| Обновлён | 2026/06/26 14:04 |
| Задача | 8239600 |
Аннотация
Перенести обработку стеков потоков в ZGC из safepoint в конкурентную фазу.
Цели
- Убрать обработку стеков потоков из safepoint в ZGC.
- Сделать обработку стеков ленивой, кооперативной, конкурентной и инкрементальной.
- Убрать из safepoint в ZGC всю остальную обработку корней отдельных потоков.
- Предоставить механизм, с помощью которого другие подсистемы HotSpot смогут лениво обрабатывать стеки.
Что не является целью
- Реализация конкурентной обработки отдельных потоков для операций safepoint, не связанных с GC, например переопределения классов, не является целью.
Критерии успеха
- Снижение задержки должно обходиться пренебрежимо малыми потерями пропускной способности.
- На типичных машинах внутри safepoint в ZGC должно тратиться меньше одной миллисекунды.
Мотивация
Сборщик мусора (GC) ZGC призван сделать паузы GC и проблемы масштабируемости в HotSpot делом прошлого. На сегодня мы перенесли из операций safepoint в конкурентные фазы все операции GC, время которых растёт с размером кучи и размером metaspace. К ним относятся разметка, перемещение, обработка ссылок, выгрузка классов и большая часть обработки корней.
В safepoint GC по-прежнему выполняются только часть обработки корней и ограниченная по времени операция завершения разметки. К этим корням относятся стеки Java-потоков и различные другие корни потоков. Такие корни проблематичны, поскольку их объём растёт с числом потоков. Когда на большой машине много потоков, обработка корней становится проблемой.
Чтобы продвинуться дальше нынешнего состояния и оправдать ожидание, что время внутри safepoint GC не превышает одной миллисекунды даже на больших машинах, мы должны перенести эту обработку отдельных потоков, включая сканирование стеков, в конкурентную фазу.
После этой работы внутри операций safepoint в ZGC не будет выполняться, по сути, ничего существенного.
Инфраструктура, созданная в рамках этого проекта, со временем может использоваться другими проектами, например Loom и JFR, для унификации ленивой обработки стеков.
Описание
Мы предлагаем решить проблему сканирования стеков с помощью stack watermark barrier. Safepoint GC будет логически делать стеки Java-потоков недействительными, переключая глобальную переменную. Каждый недействительный стек будет обрабатываться конкурентно, с отслеживанием того, что ещё осталось обработать. Выходя из safepoint, каждый поток обнаружит, что его стек недействителен, сравнив некоторые счётчики эпох, и поэтому установит stack watermark, чтобы отслеживать состояние сканирования своего стека. Stack watermark позволяет определить, находится ли данный фрейм выше watermark (если считать, что стеки растут вниз) и, следовательно, не должен использоваться Java-потоком, поскольку может содержать устаревшие ссылки на объекты.
Во всех операциях, которые либо снимают фрейм со стека, либо проходят ниже последнего фрейма стека (например, обходчики стека, возвраты и исключения), хуки будут сравнивать некоторый адрес в стеке с watermark. (Этим адресом может быть указатель фрейма, если он доступен, или указатель стека для скомпилированных фреймов, в которых указатель фрейма устранён оптимизацией, но размер фреймов достаточно постоянен.) Если адрес выше watermark, выполняется медленный путь, который исправляет один фрейм, обновляя ссылки на объекты в нём, и сдвигает watermark вверх. Чтобы возвраты оставались такими же быстрыми, как сейчас, stack watermark barrier будет использовать слегка изменённый safepoint poll. Новый poll переходит на медленный путь не только когда ожидаются safepoint (или, по сути, thread-local handshakes), но и при возврате во фрейм, который ещё не исправлен. Для скомпилированных методов это можно закодировать одним условным переходом.
Инвариант stack watermark таков: если вызываемый фрейм является последним фреймом стека, то обработаны и вызываемый, и вызывающий фреймы. Чтобы это обеспечить, при установке состояния stack watermark во время выхода из safepoint обрабатываются и вызывающий, и вызываемый фреймы. Вызываемый фрейм «взводится», так что возврат из него запускает дальнейшую обработку вызывающего фрейма, взведённым становится вызывающий фрейм, и так далее. Поэтому обработка, запускаемая раскруткой или обходом фреймов, всегда происходит на два фрейма выше фрейма, который раскручивается или обходится. Это упрощает передачу аргументов, которые должны принадлежать вызывающему фрейму, но используются вызываемым: и к вызывающему, и к вызываемому фрейму (а значит, и к дополнительным аргументам в стеке) можно свободно обращаться.
Java-потоки будут обрабатывать минимальное число фреймов, необходимое для продолжения выполнения. Конкурентные потоки GC займутся оставшимися фреймами и обеспечат, что все стеки потоков и другие корни потоков в итоге будут обработаны. Синхронизация с использованием stack watermark barrier гарантирует, что Java-потоки не вернутся во фрейм, пока GC его обрабатывает.
Альтернативы
Для обходчиков стека мы рассматривали альтернативное решение: расставить по всей VM барьеры загрузки в местах, где ссылки на объекты загружаются из стека. Мы отказались от него, потому что оно принципиально не могло гарантировать корректную обработку корней для внутренних указателей в объекты. Базовый указатель внутреннего указателя всегда должен обрабатываться после внутреннего указателя, а обходчики стека рисковали бы нарушить этот инвариант. Поэтому мы выбрали подход, при котором через обход стека обрабатывается весь фрейм, если он ещё не обработан.
Тестирование
Основные пути кода, затронутые этой работой, — это пути, которые другие тесты уже сильно нагружают, поэтому нагрузочного тестирования с помощью существующей инфраструктуры тестирования должно быть достаточно.