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

Мониторинг Java-приложений с помощью Flight Recorder

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

1. Обзор

В этом руководстве мы рассмотрим Java Flight Recorder, его концепции, основные команды и способы его использования.

2. Утилиты мониторинга Java

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

Папка bin дистрибутива JDK содержит, помимо прочего, следующие программы, которые можно использовать для профилирования и мониторинга:

  • Java VisualVM (jvisualvm.exe)
  • JConsole (jconsole.exe)
  • Управление полетами Java (jmc.exe)
  • Инструмент диагностических команд (jcmd.exe)

Мы предлагаем изучить содержимое этой папки, чтобы знать, какие инструменты есть в нашем распоряжении. Обратите внимание, что в прошлом Java VisualVM была частью дистрибутивов Oracle и Open JDK. Однако, начиная с Java 9, дистрибутивы JDK больше не поставляются с Java VisualVM . Поэтому нам следует загрузить его отдельно с сайта проекта с открытым исходным кодом VisualVM .

В этом уроке мы сосредоточимся на Java Flight Recorder. Этого нет среди упомянутых выше инструментов, потому что это не отдельная программа. Его использование тесно связано с двумя из вышеперечисленных инструментов — Java Mission Control и Diagnostic Command Tools.

3. Java Flight Recorder и его основные понятия

Java Flight Recorder (JFR) — это инструмент мониторинга, который собирает информацию о событиях в виртуальной машине Java (JVM) во время выполнения приложения Java . JFR является частью дистрибутива JDK и интегрирован в JVM.

JFR спроектирован таким образом, чтобы как можно меньше влиять на производительность работающего приложения .

Чтобы использовать JFR, мы должны активировать его. Мы можем добиться этого двумя способами:

  1. при запуске Java-приложения
  2. передача диагностических команд инструмента jcmd , когда приложение Java уже запущено

JFR не имеет отдельного инструмента. Мы используем Java Mission Control (JMC), который содержит плагин, позволяющий нам визуализировать данные, собранные JFR.

Эти три компонента — JFR , jcmd и JMC — образуют полный набор для сбора низкоуровневой информации о выполняемой Java-программе. Мы можем найти эту информацию очень полезной при оптимизации нашей программы или при ее диагностике, когда что-то идет не так.

Если на нашем компьютере установлены различные версии Java, важно убедиться, что компилятор Java ( javac ), средство запуска Java ( java ) и вышеупомянутые инструменты (JFR, jcmd и JMC) относятся к одному и тому же дистрибутиву Java. . В противном случае существует риск того, что вы не сможете увидеть какие-либо полезные данные, поскольку форматы данных JFR разных версий могут быть несовместимы.

В JFR есть две основные концепции: события и поток данных. Кратко обсудим их.

3.1. События

JFR собирает события, происходящие в JVM при запуске Java-приложения. Эти события связаны с состоянием самой JVM или с состоянием программы. У события есть имя, отметка времени и дополнительная информация (например, информация о потоке, стек выполнения и состояние кучи).

Существует три типа событий , которые собирает JFR:

  • мгновенное событие регистрируется сразу же после его возникновения
  • событие продолжительности регистрируется, если его продолжительность превышает указанный порог
  • образец события используется для выборки системной активности

3.2. Поток данных

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

JFR сохраняет данные о событиях в одном выходном файле, Flight.jfr.

Как мы знаем, дисковые операции ввода-вывода довольно затратны. Поэтому JFR использует различные буферы для хранения собранных данных перед сбросом блоков данных на диск. Все может стать немного сложнее, потому что в один и тот же момент программа может иметь несколько процессов регистрации с разными параметрами.

Из-за этого мы можем найти в выходном файле больше данных, чем запрошено, или они могут быть не в хронологическом порядке . Мы могли бы даже не заметить этого факта, если бы использовали JMC, потому что он визуализирует события в хронологическом порядке.

В некоторых редких случаях JFR может не сбросить данные (например, при слишком большом количестве событий или в случае отключения электроэнергии). В этом случае JFR пытается сообщить нам, что в выходном файле может отсутствовать часть данных.

4. Как использовать Java Flight Recorder

JFR — это экспериментальная функция, поэтому ее использование может быть изменено. Фактически, в более ранних дистрибутивах нам приходилось активировать коммерческие функции, чтобы использовать их в производстве. Однако, начиная с JDK 11, мы можем использовать его, ничего не активируя. Мы всегда можем обратиться к официальным примечаниям к выпуску Java, чтобы узнать, как использовать этот инструмент.

Чтобы JDK 8 мог активировать JFR, мы должны запустить JVM с параметрами +UnlockCommercialFeatures и +FlightRecorder .

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

4.1. Командная строка

Во-первых, мы компилируем файл программы *.java в *.class , используя стандартный компилятор Java javac .

После успешной компиляции мы можем запустить программу со следующими параметрами:

java -XX:+UnlockCommercialFeatures -XX:+FlightRecorder 
-XX:StartFlightRecording=duration=200s,filename=flight.jfr path-to-class-file

где path-to-class-file — это точка входа приложения *.class файл.

Эта команда запускает приложение и активирует запись, которая начинается сразу и длится не более 200 секунд. Собранные данные сохраняются в выходной файл Flight.jfr . Мы опишем другие варианты более подробно в следующем разделе.

4.2. Инструмент диагностической команды

Мы также можем начать регистрацию событий с помощью инструмента jcmd . Например:

jcmd 1234 JFR.start duration=100s filename=flight.jfr

До JDK 11, чтобы иметь возможность активировать JFR таким образом, мы должны запускать приложение с разблокированными коммерческими функциями:

java -XX:+UnlockCommercialFeatures -XX:+FlightRecorder -cp ./out/ com.foreach.Main

Когда приложение запущено, мы используем его идентификатор процесса для выполнения различных команд, которые имеют следующий формат:

jcmd <pid|MainClass> <command> [parameters]

Вот полный список диагностических команд:

  • JFR.start — запускает новую запись JFR.
  • JFR.check — проверяет запущенные записи JFR .
  • JFR.stop — останавливает определенную запись JFR.
  • JFR.dump — копирует содержимое записи JFR в файл.

Каждая команда имеет ряд параметров. Например, команда JFR.start имеет следующие параметры:

  • name – название записи; он служит для того, чтобы иметь возможность ссылаться на эту запись позже с помощью других команд.
  • delay – размерный параметр временной задержки начала записи, значение по умолчанию 0s
  • длительность – размерный параметр временного интервала продолжительности записи; значение по умолчанию 0s, что означает неограниченный
  • имя файла – имя файла, содержащего собранные данные
  • maxage – размерный параметр максимального возраста собираемых данных; значение по умолчанию 0s, что означает неограниченный
  • maxsize — максимальный размер буферов для собираемых данных в байтах; значение по умолчанию равно 0, что означает отсутствие максимального размера

Мы уже видели пример использования этих параметров в начале этого раздела. Полный список параметров можно найти в официальной документации Java Flight Recorded .

Хотя JFR спроектирован таким образом, чтобы как можно меньше влиять на производительность JVM и приложения, лучше ограничить максимальный объем собираемых данных, установив хотя бы один из параметров: duration , maxage или maxsize .

5. Java Flight Recorder в действии

Давайте теперь продемонстрируем JFR в действии на примере программы.

5.1. Пример программы

Наша программа вставляет объекты в список до тех пор, пока не произойдет ошибка OutOfMemoryError . Затем программа засыпает на одну секунду:

public static void main(String[] args) {
List<Object> items = new ArrayList<>(1);
try {
while (true){
items.add(new Object());
}
} catch (OutOfMemoryError e){
System.out.println(e.getMessage());
}
assert items.size() > 0;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println(e.getMessage());
}
}

Не выполняя этот код, мы можем заметить потенциальный недостаток: цикл while приведет к высокой загрузке ЦП и памяти. Давайте воспользуемся JFR, чтобы увидеть эти недостатки и, возможно, найти другие.

5.2. Начать регистрацию

Сначала мы скомпилируем нашу программу, выполнив следующую команду из командной строки:

javac -d out -sourcepath src/main src/main/com/foreach/flightrecorder/FlightRecorder.java

На этом этапе мы должны найти файл FlightRecorder.class в каталоге out/com/foreach/flightrecorder .

Теперь мы запустим программу со следующими параметрами:

java -XX:+UnlockCommercialFeatures -XX:+FlightRecorder 
-XX:StartFlightRecording=duration=200s,filename=flight.jfr
-cp ./out/ com.foreach.flightrecorder.FlightRecorder

5.3. Визуализируйте данные

Теперь мы передаем файл Flight.jfr в Java Mission Control , который является частью дистрибутива JDK. Это помогает нам визуализировать данные о наших событиях красивым и интуитивно понятным способом.

Его главный экран показывает нам информацию о том, как программа использовала ЦП во время своего выполнения. Мы видим, что CPU был загружен сильно, что вполне ожидаемо из-за цикла while :

./6ee43c9141b00ee2fae0cccca5a23d50.png

В левой части представления мы видим , среди прочего , разделы General , Memory , Code и Threads . Каждый раздел содержит различные вкладки с подробной информацией. Например, вкладка Hot Methods раздела Code содержит статистику вызовов методов:

./65c1259c472e37d5e871bc76cf12fdbb.png

На этой вкладке мы можем заметить еще один недостаток нашей программы-примера: метод java.util.ArrayList.grow(int) вызывался 17 раз для увеличения емкости массива каждый раз, когда не хватало места для добавления объекта.

В более реалистичных программах мы можем увидеть много другой полезной информации:

  • статистика о созданных объектах, когда они были созданы и уничтожены сборщиком мусора
  • подробный отчет о хронологии тредов, когда они были заблокированы или активны
  • какие операции ввода/вывода выполняло приложение

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

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

Как всегда, фрагмент кода доступен в нашем репозитории Github .