Статьи

Использование Java Flight Recorder с OpenJDK 11

Java Flight Recorder (JFR) раньше был коммерческим дополнением к Oracle JDK. Поскольку он недавно был открыт с Java Mission Control, все, кто использует OpenJDK 11, теперь могут бесплатно устранять неполадки в своих приложениях Java с помощью этого превосходного инструмента. JFR, будучи ранее проприетарным решением, может быть менее известен тем, кто полагается на предыдущие версии OpenJDK. Поэтому я подумал, что стоит написать свежий пост об использовании JFR с OpenJDK 11.

Java Flight Recorder

1. Обзор

1.1. О Java Flight Recorder

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

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

1.2. Java Flight Recorder вышел с открытым исходным кодом

Как я уже упоминал во вступлении, эта функция была проприетарной функцией Oracle JDK, и официально она была доступна только для платных клиентов Oracle. На практике вы можете включить его с -XX:+UnlockCommercialFeatures -XX:+FlightRecorder флагов -XX:+UnlockCommercialFeatures -XX:+FlightRecorder и более ранних -XX:+UnlockCommercialFeatures -XX:+FlightRecorder JVM не будут -XX:+UnlockCommercialFeatures -XX:+FlightRecorder наличия лицензионного ключа или чего-либо подобного.

Марк Рейнхольд из Oracle хотел ускорить продвижение Java и получил вдохновение от некоторых операционных систем Linux, выпуск которых составлял шесть месяцев. Я думаю, что он мог подумать об Ubuntu, хотя он не упомянул это именно. Тем не менее, Java SE начиная с версии 9 действительно имеет предсказуемый шестимесячный цикл выпуска.

Короче говоря, чтобы сократить время выпуска, теперь они работают на единой кодовой базе, что сделало сборки Oracle JDK и OpenJDK взаимозаменяемыми. В конце концов, начиная с Java 11, Oracle предоставляет версии JDK под открытым исходным кодом GPL и коммерческой лицензией . Если вы привыкли получать двоичные файлы Oracle JDK бесплатно, вместо этого скачайте сборки OpenJDK , они функционально идентичны.

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

1.3. Различия в упаковке JFR

Oracle JDK 11 выдает предупреждение при использовании опции -XX:+UnlockCommercialFeatures , тогда как OpenJDK не распознает эту опцию и сообщает об ошибке.

1.4. Java Mission Control также был открытым исходным кодом

JMC — это клиентский инструмент, используемый для открытия тех записей о времени производства и диагностических записей, созданных JFR . JMC также предоставляет другие функции, такие как консоль JMX и анализатор дампа кучи. Выпуски Oracle JDK с 7 по 10 содержат JMC, но он был отделен и теперь доступен для отдельной загрузки .

JMC также недавно был открыт с открытым исходным кодом, и это означает, что теперь весь набор инструментов (JFR + JMC) доступен любому, кто использует OpenJDK 11. На момент написания первой версии 7 JMC с открытым исходным кодом еще не достиг GA, но ранние сборки доступа обеспечены.

Java Flight Recorder

2. Использование регистратора полета

Я не использовал JFR в производстве постоянно, потому что это было бы нарушением лицензии Oracle JDK. Для развития все может быть использовано в соответствии с моими знаниями. Таким образом, пользователи Oracle JDK — без контракта на поддержку — должны были в конечном итоге вынуждены воспроизводить проблемы производительности локально на своих машинах разработки.

Хорошо, тогда давайте посмотрим код. Это будет простая демонстрация самых основ Java Flight Recorder, и я намеренно затрудняюсь, чтобы дать нам что-то для отладки.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public class OOMEGenerator {
 
  static BlockingQueue<byte[]> queue = new LinkedBlockingQueue<>();
 
  public static void main(String[] args) {
    new Thread(new Consumer()).start();
    new Thread(new Producer()).start();
  }
 
  static class Producer implements Runnable {
 
    public void run() {
      while (true) {
        queue.offer(new byte[3 * 1024 * 1024]);
 
        try {
          Thread.sleep(50);
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
      }
    }
 
  }
 
  static class Consumer implements Runnable {
 
    public void run() {
      while (true) {
        try {
          queue.take();
          Thread.sleep(100);
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
      }
    }
 
  }
 
}

Для демонстрации классического OOME мы могли бы просто добавить новые объекты в коллекцию. Я выбрал этот, потому что часто вижу этот конкретный образец в производстве. То есть существует два (или более) компонента, и некоторые из них создают новые объекты, а некоторые из них потребляют эти объекты. Проблема связана с тем, что емкость этого внутреннего буфера, через который взаимодействуют компоненты, потенциально неограниченна.

Когда я говорю «потенциально неограниченный», я имею в виду, что размер буфера, умноженный на размер среднего объекта, настолько велик и через некоторое время он просто пожирает все пространство кучи. Часто это может OutOfMemoryError часы, дни или неделю, но OutOfMemoryError произойдет в конце концов.

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

2.1. Включить JFR

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
% java \
    -Xmx1024m \
    -Xlog:gc*=debug:file=gc.log:utctime,uptime,tid,level:filecount=10,filesize=128m \
    -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=heapdump.hprof \
    -XX:StartFlightRecording=\
        disk=true, \
        dumponexit=true, \
        filename=recording.jfr, \
        maxsize=1024m,\
        maxage=1d,\
        settings=profile \
        path-to-gc-roots=true \
    OOMEGenerator
 
Started recording 1.
Use jcmd 5469 JFR.dump name=1 to copy recording data to file.
 
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at nl.wizenoze.storm.OOMEGenerator.main(OOMEGenerator.java:12)
 
22.31s user 3.46s system 24% cpu 1:43.94 total

Я использую эти настройки отладки в производстве. В этом смысле эти аргументы JVM также служат примечанием для меня, когда они мне понадобятся, я найду их здесь.

Что касается JFR, это выделенная часть (строки 5-11). По умолчанию ни максимальный размер записи, ни максимальный возраст или записанные данные не ограничены. Я экспериментировал с различными настройками, и применение maxage и maxsize оказалось полезным. В более ранних версиях JDK у JFR было больше настроек, но они упростили их в версии 11.

Я хотел бы обратить ваше внимание на опцию dumponexit . При нормальных обстоятельствах при нормальных условиях запись записывается на диск всякий раз, когда захват данных прекращается. Это естественно случается, когда JVM завершается. Однако, согласно моему опыту, когда завершение является ненормальным, например, когда пространство кучи заканчивается, запись может иметь нулевой размер байта. Хотя в документации о настройках JFR не очень ясно, в чем dumponexit отличие dumponexit , я заметил, что его применение dumponexit для сбора данных с проблемных JVM.

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

2.2. Захват записей по требованию

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

1
2
3
% jcmd PID JFR.check name=1
14647:
Recording 1: name=1 maxsize=1.0GB maxage=1d (running)

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

1
%jcmd 5469 JFR.dump name=1 filename=dump.jfr

Существуют и другие способы устранения неполадок, предоставляемые утилитой JCMD.

2,3. Анализ записей JFR

Как я уже упоминал ранее, JMC нужно загружать отдельно. Хотя это только ранний доступ, я нашел его полностью пригодным для использования без особых проблем. В JMC 6 добавлен экран результатов автоматического анализа, чтобы помочь инженерам быстрее диагностировать проблемы. Я использовал более старую версию JMC 5.5 и нашел ее полезной, и она действительно дает полезные советы. Он правильно определил OOMEGenerator$Producer как источник или генерируемые крупные объекты, а также советует сбалансировать скорость распределения между потоками.

Java Flight Recorder

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

Java Flight Recorder

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

Java Flight Recorder

3. Предостережения

  • Записи JFR не имеют обратной совместимости — записи, созданные OpenJDK 11, не имеют обратной совместимости, и более старые версии JMC (пробные версии 5.5 и 6) не открывали их
  • JMC 7 все еще является ранним выпуском доступа — функциональность может измениться в GA, и некоторые ошибки могут скрываться здесь и там
  • В официальном образе Docker есть ошибка — препятствующая запуску JVM при включенной JFR
  • JMC 7 не может анализировать файлы HPROF — хотя вики OpenJDK заявляют, что он может это сделать

4. Вывод

Java Flight Recorder (JFR) — это инструмент профилирования, используемый для сбора данных диагностики и профилирования из работающего приложения Java. Он собирает данные о запущенных потоках, циклах GC, блокировках, сокетах, использовании памяти и о многом другом. JFR вместе с Java Mission Control, который является инструментом для анализа записи, были с открытым исходным кодом и больше не являются проприетарными продуктами Oracle. Этот шаг Oracle делает OpenJDK более привлекательным для разработчиков.

Начиная с версии 11 Java Mission Control не входит в комплект JDK, но доступен для отдельной загрузки .

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

JFR и JMC являются новыми вещами в OpenJDK-пространстве с открытым исходным кодом, и OpenJDK 11 также очень нов во время написания, поэтому должно пройти некоторое время, чтобы позволить этим инструментам стать более зрелыми.

Смотрите оригинальную статью здесь: Использование Java Flight Recorder с OpenJDK 11

Мнения, высказанные участниками Java Code Geeks, являются их собственными.