JEP 312: Thread-Local Handshakes
Handshake-операции, локальные для потока
| Ответственный | Robbin Ehn |
| Тип | Feature |
| Область | JDK |
| Статус | Closed / Delivered |
| Выпуск | 10 |
| Компонент | hotspot / runtime |
| Обсуждение | hotspot dash dev at openjdk dot java dot net |
| Блокирует | JEP 333: ZGC: A Scalable Low-Latency Garbage Collector (Experimental) |
| Рецензенты | Mikael Vidstedt |
| Одобрен | Mikael Vidstedt |
| Создан | 2017/08/01 13:30 |
| Обновлён | 2026/06/26 14:06 |
| Задача | 8185640 |
Аннотация
Добавить способ выполнять обратный вызов в потоках без глобального safepoint виртуальной машины. Сделать возможной и дешёвой остановку отдельных потоков, а не только выбор между остановкой всех потоков или ни одного.
Что не является целью
Эффективно реализовать это на всех поддерживаемых архитектурах может оказаться невозможно. Поначалу поддержка всех процессорных архитектур и всех их версий не входит в цели.
Критерии успеха
-
Новый механизм не снижает производительность в стандартных бенчмарках больше чем на 1%.
-
Новый механизм не увеличивает время, необходимое для достижения традиционного глобального safepoint.
Мотивация
Возможность останавливать отдельные потоки можно применить во многих случаях:
-
Улучшить отзыв смещённых блокировок (biased lock revocation): останавливать для отзыва смещения только отдельные потоки, а не все.
-
Снизить общее влияние на задержку виртуальной машины от разных запросов обслуживания (serviceability), например получения трассировок стека всех потоков. Если в виртуальной машине много Java-потоков, такая операция может быть медленной.
-
Сделать выборку трассировок стека безопаснее за счёт меньшей зависимости от сигналов.
-
Убрать часть барьеров памяти с помощью так называемых приёмов Asymmetric Dekker Synchronization, выполняя handshake-операции с Java-потоками. Например, коду условной пометки карт (conditional card mark), который по своей природе нужен G1 и используется CMS, барьеры памяти не понадобятся. Благодаря этому можно оптимизировать барьер G1 после записи (post write barrier) и удалить ветвления, которые пытаются обойти барьер памяти.
Всё это поможет виртуальной машине добиться меньшей задержки за счёт уменьшения числа глобальных safepoint.
Описание
Handshake-операция — это обратный вызов, который выполняется для каждого JavaThread, пока этот поток находится в состоянии, безопасном с точки зрения safepoint. Обратный вызов выполняет либо сам поток, либо VM-поток, который при этом удерживает поток в заблокированном состоянии. Главное отличие safepoint от handshake-операции в том, что операция для каждого потока будет выполнена во всех потоках как можно скорее, и каждый поток продолжит работу, как только завершится его собственная операция. Если известно, что JavaThread выполняется, handshake-операцию можно провести и с этим единственным JavaThread.
В первой реализации будет ограничение: одновременно может выполняться не больше одной handshake-операции. Однако операция может охватывать любое подмножество всех JavaThread. VM-поток будет координировать handshake-операцию через VM-операцию, которая фактически не даст возникнуть глобальным safepoint во время handshake-операции.
Текущая схема safepoint изменяется: обращение идёт косвенно, через указатель, отдельный для каждого потока, и так можно заставить выполнение одного потока прерваться на защитной странице. По сути, всегда будут существовать две страницы опроса: одна всегда защищена, другая всегда не защищена. Чтобы заставить поток уступить выполнение, виртуальная машина меняет указатель соответствующего потока так, чтобы он указывал на защищённую страницу.
Поначалу handshake-операции, локальные для потока, будут реализованы на x64 и SPARC. На остальных платформах будут по-прежнему использоваться обычные safepoint. Новая штатная опция -XX:ThreadLocalHandshakes (значение по умолчанию true) позволяет пользователям выбрать обычные safepoint на поддерживаемых платформах.
Альтернативы
Рассматривались несколько альтернатив:
-
Генерировать вместо этого условные переходы. Это расходует состояние предсказателя ветвлений и не так компактно, как одна загрузка. Эксперименты в этой области показали, что производительность условных переходов может сильно зависеть от конкретной микроархитектуры целевого процессора. Ещё один недостаток подхода с условными переходами в том, что для каждого safepoint с условным переходом нужно генерировать соответствующую заглушку, которая обеспечивает возврат к месту опроса.
-
Есть идея, которая требует пожертвовать ещё одним регистром и затем загружать в сам регистр значение по адресу, хранящемуся в этом регистре, при условии что в регистре лежит адрес его собственного поля, локального для потока. Handshake-операция, локальная для потока, начиналась бы с того, что поле меняется на
NULL. При следующем опросе регистр получил бы значениеNULL, а при втором опросе загрузка вызвала бы прерывание. Это требует глобально пожертвовать регистром, прерывания обходятся дороже, и после запроса на остановку потока для достижения safepoint в среднем понадобится вдвое больше опросов. Преимущество в том, что теоретически это меньше влияет на выполнение приложения. -
Ранее был создан прототип, в котором глобальная страница опроса оставалась как есть, но в коде виртуальной машины перехватывались только действительно целевые потоки. Потоки, не являющиеся целями handshake-операции, просто возвращались из обработчика сигнала и продолжали выполнение. Недостаток этого подхода в том, что если целевой поток отвечает медленно, это может вызвать шквал сигналов для других Java-потоков, так как страницу опроса нельзя снять с защиты, пока целевой поток не ответит.