JEP 518: JFR Cooperative Sampling
Кооперативная выборка стеков в JFR
| Ответственный | Markus Grönlund |
| Тип | Feature |
| Область | Implementation |
| Статус | Closed / Delivered |
| Выпуск | 25 |
| Компонент | hotspot / jfr |
| Обсуждение | hotspot dash jfr dash dev at openjdk dot org |
| Трудоёмкость | M |
| Длительность | M |
| Рецензенты | Erik Gahlin, Vladimir Kozlov |
| Одобрен | Vladimir Kozlov |
| Создан | 2025/02/19 14:12 |
| Обновлён | 2025/06/10 16:31 |
| Задача | 8350338 |
Аннотация
Повысить стабильность JDK Flight Recorder (JFR) при асинхронной выборке стеков Java-потоков. Для этого обходить стеки вызовов только в safepoint (точках безопасной остановки) и при этом свести к минимуму safepoint bias (смещение выборки к safepoint).
Мотивация
Работающая программа потребляет вычислительные ресурсы: память, такты процессора и время выполнения. Профилировать программу значит измерять, сколько таких ресурсов потребляют её отдельные элементы. Например, профиль может показать, что один метод потребляет 20 % ресурса, а другой всего 0,1 %.
Профилирование помогает сделать программу эффективнее, а работу разработчиков продуктивнее, потому что показывает, какие элементы программы стоит оптимизировать. Без профилирования мы можем оптимизировать метод, который и так потреблял мало ресурсов: на общую производительность программы это почти не повлияет, а силы будут потрачены впустую. Например, если метод занимает 0,1 % общего времени выполнения программы и мы ускорим его в десять раз, время выполнения программы сократится всего на 0,09 %.
JFR (JDK Flight Recorder) — средство профилирования и мониторинга в JDK. Основа JFR — механизм с низкими накладными расходами для записи событий, которые порождает HotSpot JVM или код программы. Некоторые события, например загрузка класса, записываются каждый раз, когда происходит действие. Другие, например события для профилирования, записываются путём статистической выборки активности программы, пока она потребляет ресурс. Различные события JFR можно включать и выключать: во время разработки можно собирать более подробную информацию с бо́льшими накладными расходами, а в эксплуатации — менее подробную с меньшими.
JFR может строить профиль времени выполнения, который показывает, какие элементы программы занимают значительную долю реального времени (wall-clock time). Для этого JFR делает выборку стеков выполнения потоков программы через фиксированные интервалы, например 20 мс. Каждая выборка порождает событие JFR, содержащее трассировку стека. Такие инструменты, как jfr и JDK Mission Control, могут свести поток таких событий в текстовый или графический профиль.
Чтобы получить трассировку стека потока программы, поток выборки JFR должен приостановить целевой поток и разобрать фреймы вызовов в его стеке. HotSpot JVM хранит метаданные, по которым разбираются фреймы стека, но эти метаданные корректны только тогда, когда поток приостановлен в строго определённых местах кода, которые называются safepoint. Однако если делать выборку стеков только в safepoint, мы, скорее всего, столкнёмся с проблемой safepoint bias: есть риск потерять точность, потому что часто выполняемый участок кода может находиться далеко от любой safepoint. Проблема safepoint bias хорошо известна и подробно исследована.
Чтобы избежать проблемы safepoint bias, JFR делает выборку стеков потоков программы асинхронно: приостанавливает потоки и разбирает их стеки в местах кода, которые не обязательно являются safepoint. Поскольку вне safepoint корректность метаданных для разбора фреймов стека не гарантируется, поток выборки JFR строит трассировку стека с помощью эвристик.
К сожалению, эти эвристики разбора стека неэффективны и, что хуже, при неверном результате могут привести к аварийному завершению JVM. JFR пытается предотвратить такие сбои с помощью платформенно-зависимых механизмов защиты, но эти механизмы могут не сработать при параллельной активности, например при выгрузке классов.
Описание
Мы перерабатываем механизм выборки JFR, чтобы он не зависел от рискованных эвристик разбора стека. Вместо этого мы разбираем стеки потоков только в safepoint.
Чтобы избежать проблемы safepoint bias, мы делаем выборку кооперативно. Когда наступает время выборки, поток выборки JFR по-прежнему приостанавливает целевой поток. Однако вместо того чтобы пытаться разобрать стек, он лишь записывает счётчик команд и указатель стека целевого потока в запрос на выборку и добавляет его во внутреннюю очередь, локальную для потока. Затем он делает так, чтобы целевой поток остановился в ближайшей safepoint, и возобновляет поток.
Целевой поток работает как обычно до ближайшей safepoint. В этот момент код обработки safepoint проверяет очередь. Если в ней есть запросы на выборку, то для каждого из них он восстанавливает трассировку стека с поправкой на safepoint bias и порождает событие выборки времени выполнения JFR.
Помимо безопасности, у этого подхода есть ещё несколько преимуществ:
-
Создание запроса на выборку почти не требует работы, и его можно выполнять в ответ на аппаратное событие или внутри обработчика сигнала.
-
Код, который строит трассировки стека и порождает события, становится проще. Например, при работе в целевом потоке он может динамически выделять память, чего не мог делать при работе в потоке выборки.
-
У потока выборки меньше работы, поскольку ему не нужно выполнять эвристики, и это улучшает масштабируемость.
Этот подход хорошо работает, когда целевой поток выполняет Java-код, интерпретируемый или скомпилированный, но не когда целевой поток выполняет нативный код. В этом случае мы продолжаем использовать существующий подход.
Дальнейшая работа
Новый подход не устраняет safepoint bias полностью. В некоторых ситуациях, например при выборке внутри метода, для которого в HotSpot JVM есть intrinsic-реализация, разобрать стек может быть невозможно. В таких случаях записанная трассировка стека будет отражать последний фрейм Java-стека, что вносит некоторое смещение. Мы планируем решить эту проблему в дальнейшей работе.
Альтернативы
В HotSpot JVM уже есть внутренний, но неподдерживаемый механизм AsyncGetCallTrace, которым пользуются некоторые сторонние инструменты. К сожалению, этот механизм опирается на такие же рискованные эвристики разбора стека, какие сейчас использует JFR, но без какой-либо защиты от сбоев, поэтому он ещё рискованнее. Ещё один недостаток в том, что он основан на POSIX-сигнале SIGPROF, у которого нет аналога в Windows.
Тестирование
Это исключительно изменение реализации. Существующих модульных, интеграционных и нагрузочных тестов будет достаточно.
Зависимости
Реализация JEP 509 (JFR CPU-Time Profiling) использует механизм, который вводится в этом JEP.