JavaSeniorTechnical

Что такое JFR и как использовать его для диагностики production performance?

JFR — встроенный в JVM профайлер с оверхедом менее 1%, пригодный для production. Запускается через флаг -XX:StartFlightRecording или jcmd на живом процессе; записывает GC, блокировки, I/O, аллокации, исключения. Анализируется через JMC GUI или jfr CLI.

Java Flight Recorder (JFR)

JFR — встроенный в JVM низкооверхедный профайлер и инструмент трассировки, доступный с Java 11 без лицензионных ограничений. Он записывает события JVM (GC, компиляция JIT, блокировки, аллокации, I/O, исключения, сетевые вызовы) в бинарный файл .jfr с накладными расходами менее 1% CPU — пригоден для production.

Запуск записи

Вариант 1: при старте JVM

java \
  -XX:StartFlightRecording=duration=60s,filename=/tmp/app.jfr,settings=profile \
  -jar app.jar

Вариант 2: на живом процессе через jcmd

# Найти PID
jps -l

# Начать запись
jcmd <PID> JFR.start duration=120s filename=/tmp/app.jfr settings=profile name=diag

# Проверить статус
jcmd <PID> JFR.check

# Остановить досрочно и сбросить файл
jcmd <PID> JFR.stop name=diag

# Сбросить файл без остановки (dump snapshot)
jcmd <PID> JFR.dump name=diag filename=/tmp/snapshot.jfr

Настройки профилей

  • settings=default — минимальный оверхед (~0.1%), базовые события.
  • settings=profile — расширенный набор (аллокации, блокировки, I/O), оверхед ~1%.
  • Кастомные профили: XML-файлы в $JAVA_HOME/lib/jfr/, редактируются через JMC Mission Control.

Анализ записи

# Вывести все события определённого типа
jfr print --events jdk.GarbageCollection app.jfr

# Фильтр по времени
jfr print --events jdk.SocketRead --time "2025-01-15T10:00:00Z/PT5M" app.jfr

# Краткая сводка
jfr summary app.jfr

# Список всех типов событий в файле
jfr metadata app.jfr | grep "^Event"

Анализ через JDK Mission Control (JMC)

JMC — GUI для JFR, скачивается отдельно с adoptium.net/jmc. Ключевые вкладки:

  • Method Profiling — горячие методы (CPU sampling), как flame graph.
  • Memory — TLAB аллокации, GC паузы, утечки.
  • Threads — блокировки, deadlock detection.
  • I/O — медленные файловые и сетевые операции.
  • Exceptions — частые исключения (даже пойманные).

Программный доступ через JFR API

import jdk.jfr.*;
import jdk.jfr.consumer.*;
import java.nio.file.Path;
import java.time.Duration;

// Создать кастомное событие
@Label("Database Query")
@Description("Tracks slow SQL queries")
@Category("Application")
@StackTrace(false)
public class DbQueryEvent extends Event {
    @Label("SQL")
    public String sql;

    @Label("Duration ms")
    public long durationMs;
}

// Использование
void executeQuery(String sql) {
    DbQueryEvent event = new DbQueryEvent();
    event.begin();
    try {
        // execute sql...
    } finally {
        event.sql = sql;
        event.durationMs = event.getDuration().toMillis();
        event.commit();
    }
}

// Чтение .jfr файла программно
void analyzeRecording(Path jfrFile) throws Exception {
    try (RecordingFile rf = new RecordingFile(jfrFile)) {
        while (rf.hasMoreEvents()) {
            RecordedEvent event = rf.readEvent();
            if (event.getEventType().getName().equals("jdk.GarbageCollection")) {
                Duration pause = event.getDuration();
                System.out.println("GC pause: " + pause.toMillis() + "ms");
            }
        }
    }
}

Диагностика типичных production-проблем

  • CPU spike: jfr print --events jdk.ExecutionSample app.jfr → найти горячие методы; сравнить frame distribution.
  • Утечка памяти: события jdk.ObjectAllocationInNewTLAB + jdk.OldObjectSample — найти аллоцирующий код и объекты, доживающие до old gen.
  • Медленные запросы: jdk.SocketRead, jdk.SocketWrite, jdk.FileRead — фильтровать по duration > threshold.
  • Lock contention: jdk.JavaMonitorWait, jdk.JavaMonitorEnter — найти монитор с наибольшим суммарным временем ожидания.

Подводные камни

  • Размер файла не ограничен по умолчанию. При длинных записях с settings=profile файл может занять несколько GB; используйте maxsize=500MB или maxage=1h.
  • Оверхед не нулевой при profile. ~1% CPU и дополнительное давление на GC от буферов событий — допустимо для production, но учитывайте при нагрузочном тестировании.
  • jcmd требует того же пользователя или root. Команда jcmd <PID> JFR.start от другого пользователя упадёт с Permission denied — убедитесь, что запускаете от имени пользователя JVM.
  • Кастомные события теряются без регистрации. Если класс-событие выгружен (classloader leak), события могут не записаться; всегда регистрируйте через FlightRecorder.register(DbQueryEvent.class).
  • Временные метки в UTC. JFR пишет время в UTC; при сравнении с логами приложения убедитесь, что временные зоны совпадают.
  • Не все методы видны в Method Profiling. JIT-компиляция может inline-ить методы, убирая их из стека; компилируйте с -XX:+PreserveFramePointer для точных стеков.
  • JMC не входит в JDK. Нужно скачивать отдельно; в CI-среде используйте CLI (jfr print) или библиотеку jdk.jfr.consumer.

Common mistakes

  • Отвечать определением без production-сценария.
  • Не называть runtime boundary, security boundary или failure mode.
  • Игнорировать версию API, observability и тестовую проверку.

What the interviewer is testing

  • Объясняет механизм своими словами и без выдуманных API.
  • Называет реальные риски, диагностику и критерий корректности.
  • Связывает ответ с текущей документацией и миграционными ограничениями.

Sources

Related topics