Как определить путь к запущенной java машине
Перейти к содержимому

Как определить путь к запущенной java машине

  • автор:

Кофе-брейк #82. 41 инструмент для мониторинга JVM и производительности Java-приложений

Java-университет

Кофе-брейк #82. 41 инструмент для мониторинга JVM и производительности Java-приложений - 1

Источник: JVMwhisperer Я попытался создать исчерпывающий список инструментов мониторинга JVM и сопутствующих продуктов. Надеюсь, это поможет вам в вашей работе. Инструменты перечисляются в алфавитном порядке, без каких-либо предпочтений, связанных с местом в списке.

  1. Apache Netbeans может использоваться не только как IDE, но и как отладчик. Работает во всех операционных системах, поддерживающих Java: Windows, Linux, Mac OSX и BSD.
  2. Apache Skywalking — инструмент мониторинга производительности приложений для распределенных систем, специально разработанный для микросервисов, облачных и контейнерных (Docker, Kubernetes, Mesos) архитектур, включая Java.
  3. Когда дело доходит до бесплатных инструментов для мониторинга приложений Java, обычно выбирают JConsole и VisualVM. AppDynamics предоставляет третий вариант, который поможет вам в мониторинге производительности веб-приложений.
  4. Cobertura — это скорее инструмент разработки, чем мониторинга, но я считаю, что весь код должен быть протестирован перед запуском в производство, а Cobertura — это бесплатный инструмент Java, который вычисляет процент кода, доступного для тестов. Его можно использовать для определения того, какие части вашей программы Java не покрыты тестами. Основан на jcoverage.
  5. Flamegraph Datadog анализирует каждый запрос Java для обнаружения и устранения проблем с производительностью приложений в распределенной системе.
  6. Dynatrace контролирует и оптимизирует производительность приложений Java для всего стека приложений, включая базы данных и службы.
  7. Eclipse Memory Analyzer — набор инструментов общего назначения для анализа дампов кучи Java. Также он сообщает о возможных утечках и антипаттернах потребления памяти. Основная область применения — ошибки, связанные с нехваткой или повышенным потреблением памяти.
  8. Fusion Reactor — инструмент мониторинга Java, который позволяет находить проблемы с производительностью в Java-приложении или сервере, анализируя код, поток и память.
  9. GCeasy — первый инструмент для анализа журнала сборки мусора, работающий на основе машинного обучения. GCeasy автоматически обнаруживает проблемы в журналах JVM и Android GC и рекомендует вариант их решения.
  10. Glowroot предоставляет информационную сводку о приложении, имеет гибкие настройки, формирует диаграммы для визуализации разбивки по времени отклика. С его интерактивным пользовательским интерфейсом можно контролировать Java-приложение как с настольных компьютеров, так и с мобильных устройств.
  11. JavaMelody выполняет мониторинг приложений Java или Java EE в средах контроля качества и производственных средах. Это не инструмент для имитации запросов от пользователей — это инструмент для измерения и расчета статистики реальной работы приложения в зависимости от степени его использования.
  12. Подключаемый модуль Jet Brains Memory View показывает общее количество объектов в куче, сгруппированных по имени их класса. Когда вы пошагово выполняете программу, столбец Diff показывает, как количество объектов изменяется между остановками отладчика. Таким образом, вы можете легко увидеть, как код влияет на кучу.
  13. JProfiler — простой и мощный инструмент профилирования. Настройка сеансов в нем проста, сторонние интеграции упрощают начало работы, а данные профилирования представлены в естественном виде.
  14. JRat Java Runtime Analysis Toolkit — профилировщик производительности с открытым исходным кодом для платформы Java. JRat отслеживает выполнение приложения и сохраняет измерения производительности. Затем эти данные можно просмотреть и проанализировать с помощью JRat Desktop, приложения Swing.
  15. С помощью инструмента Kamon можно определить причину медленных запросов и другие ошибки. Разработчику достаточно навести и нажать мышь, чтобы найти endpoints, запросы к базе данных и вызовы API, которые влияют на взаимодействие с пользователем.
  16. Lightstep автоматически обнаруживает изменения в приложении, инфраструктуре и взаимодействии с пользователем и выделяет конкретные причины.
  17. Applications Manager JRE отслеживает различные метрики, включая память JVM, сборку мусора, потоки и другое. Отличный инструмент для тщательного мониторинга производительности JRE.
  18. MoSKito Monitoring проверяет работоспособность приложения. Имеет открытый код, поддерживает микросервисы, отличный выбор для специалистов DevOps и инженеров SR.
  19. Nagios — каталог модулей, сценариев и плагинов для приложений и серверов Java.
  20. New Relic позволяет отслеживать практически все: от проблем с производительностью до мелких ошибок в коде. Каждую минуту Java-агент отправляет метрические временные интервалы и данные о событиях в пользовательский интерфейс New Relic, где владелец этих данных может войти в систему и использовать эту информацию, чтобы увидеть, как работает сайт или приложение.
  21. Java VisualVM — инструмент, который предоставляет визуальный интерфейс для просмотра подробной информации о приложениях Java во время их работы на виртуальной машине Java (JVM), а также для устранения неполадок и профилирования этих приложений. Различные дополнительные инструменты, включая Java VisualVM, поставляются с дистрибутивом Sun Java Development Kit (JDK).
  22. JConsole — инструмент мониторинга, который соответствует спецификации Java Management Extensions (JMX). JConsole использует обширный инструментарий виртуальной машины Java (Java VM) для предоставления информации о производительности и потреблении ресурсов приложениями, работающими на платформе Java.
  23. Java Flight Recorder и JDK Mission Control — комбинация инструментов для постоянного сбора подробной информации о времени выполнения, что позволяет анализировать инциденты постфактум. Java Flight Recorder — это платформа для профилирования и сбора событий, встроенная в Oracle JDK. С ее помощью можно собирать подробную низкоуровневую информацию о поведении виртуальной машины Java (JVM) и приложения Java. JDK Mission Control — это расширенный набор инструментов, который позволяет эффективно и детально анализировать обширный объем данных, собранных Java Flight Recorder.
  24. JMap — утилита, которая при использовании jmap с файлом процесса или ядром без каких-либо параметров командной строки печатает список загруженных общих объектов (вывод аналогичен утилите pmap в операционной системе Oracle Solaris). Для получения конкретной информации, можно использовать опции -heap, -histo или -permstat.
  25. Jps — утилита, полезная в средах со встроенной виртуальной машиной. Позволяет легко распознавать процессы Java в списке процессов.
  26. JStack — утилита, которая из командной строки присоединяется к указанному процессу или файлу ядра и печатает трассировку стеков всех потоков, которые прикреплены к виртуальной машине. Также утилита обнаруживает тупиковые ситуации.
  27. JStat — утилита, которая предоставляет информацию о производительности и потреблении ресурсов запущенных приложений.
  28. The jstatd Daemon — сервер приложений RMI, который контролирует создание и завершение каждого инструментированного Java HotSpot и предоставляет интерфейс, позволяющий средства удаленного мониторинга для подключения к JVM, выполняющихся на локальном хосте.
  29. Visualgc — инструмент, который обеспечивает графическое представление системы сборки мусора. Использует встроенные инструменты виртуальной машины Java HotSpot.
  30. Pinpoint — это инструмент APM (Application Performance Management, Управление производительностью приложений) для крупномасштабных распределенных систем, написанный на Java, PHP или Python. Pinpoint предоставляет решение, которое помогает проанализировать общую структуру системы и то, как компоненты в них взаимосвязаны благодаря отслеживанию транзакций в распределенных приложениях.
  31. Scouter — еще один инструмент APM (управление производительностью приложений) с открытым исходным кодом.
  32. Sematext Experience — инструмент мониторинга, который позволяет отслеживать производительность приложения и получать предупреждения всякий раз, когда он обнаруживает аномалии, влияющие на работу конечного пользователя.
  33. Sematext Logs — служба ведения журналов, которая позволяет централизовать журналы из различных источников, таких как приложения, микросервисы, операционные системы, различные устройства и, конечно же, приложения на основе Java.
  34. Sematext Cloud — это платформа наблюдения с широким выбором функций. Благодаря простой и удобной настройке, мониторинг Java-приложения выполняется легко и быстро. С предварительно созданными готовыми панелями мониторинга можно начать мониторинг приложения на основе JVM сразу после завершения установки агента. Sematext Logs также позволяет анализировать журналы с помощью функции автоматического обнаружения журналов для приложений Java или отправлять их через одного из поддерживаемых поставщиков журналов.
  35. Site24x7 — инструмент, с помощью которого разработчики могут легко выявить медленные внутренние вызовы (методы) в коде Java и просмотреть весь путь в виде дерева. Трассировка будет отображать последовательность вызовов URL, включая определенные пользователем методы.
  36. Java Solar Winds — инструменты, которые помогают улучшить и оптимизировать производительность Java-приложений.
  37. Stackify — набор инструментов для мониторинга производительности Java-приложений.
  38. Stage Monitor — решение с открытым исходным кодом, чтобы мониторить производительность серверных приложений Java.
  39. VisualVM — визуальный инструмент с поддержкой Java 16 и Apple M1, объединяющий инструменты командной строки JDK и облегченные возможности профилирования. Предназначен как для разработки, так и для использования во время производства.
  40. XRebel делает то, чего не могут традиционные инструменты профилирования. Он позволяет разработчикам отслеживать влияние своего кода от начала до конца — даже в распределенных приложениях.
  41. YourKit Java Profiler — инструмент профилирования Java-приложений как на стадии разработки, так и на стадии производства.

Управление Java Flight Recorder

Не так давно в мире Java случилось грандиозное событие. Во всех актуальных версиях OpenJDK стал доступен Java Flight Recorder (или просто JFR).

Что такое Java Flight Recorder?

JFR – это механизм легковесного профилирования Java-приложения. Он позволяет записывать и в последствии анализировать огромное количество метрик и событий, происходящих внутри JVM, что значительно облегчает анализ проблем. Более того, при определённых настройках его накладные расходы настолько малы, что многие (включая Oracle) рекомендуют держать его постоянно включённым везде, в том числе прод, чтобы в случае возникновения проблем сразу иметь полную картину происходившего с приложением. Просто мечта любого SRE!

Раньше этот механизм был доступен только в коммерческих версиях Java от корпорации Oracle версии 8 и более ранних. В какой-то момент его реимплементировали с нуля в OpenJDK 12, затем бекпортировали в OpenJDK 11, которая является LTS-версией. Однако вот OpenJDK 8 оставалась за бортом этого праздника жизни. Вплоть до выхода апдейта 8u272, в который наконец-то тоже бекпортировали JFR. Теперь все (за редким исключением) пользователи OpenJDK могут начинать использовать эту функциональность.

Но вот незадача: большая часть документации в интернете относится к старой, коммерческой, версии JFR и во многом не соответствует версии, которая присутствует в OpenJDK. Да и та, что есть, весьма скудная и не способствует пониманию того, как это всё использовать.

В предлагаемой вашему вниманию статье я расскажу, как управлять работой JFR и как его настраивать.

Активация через параметры JVM

Активировать JFR можно несколькими путями. Один из них – задать соответствующие параметры при запуске Java-приложения. Для этого необходимо использовать два параметра:

  • -XX:StartFlightRecording (активирует JFR и определяет основные параметры его работы),
  • -XX:FlightRecorderOptions (задаёт дополнительные параметры работы).

Каждая из этих опций может быть дополнена настройками, например:

java -XX:StartFlightRecording=filename=/path/to/record/file.jfr
java -XX:StartFlightRecording:filename=/path/to/record/file.jfr

задаёт путь к файлу, куда будет записана вся собранная JFR информация после завершения записи (или остановки приложения).

Опций может быть несколько. В таком случае они разделяются запятыми (без пробелов!).

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

XX:StartFlightRecording

Посмотреть список опций можно в первоисточнике. В исходном файле OpenJDK. Для OpenJDK 11 это файл src/hotspot/share/jfr/dcmd/jfrDcmds.cpp .

Опция Описание Умолчание
name Имя, по которому можно идентифицировать запись, например: «My Recording». Одновременно может быть запущено несколько разных записей. Чтобы их как-то различать и управлять ими независимо, используется это имя.
settings Файл(ы) с настройками, какие конкретно метрики и как профилировать. Это может быть или предопределённый профиль (см. JRE_HOME/lib/jfr), или путь к своему файлу с настройками. Позже рассмотрим, как сформировать такой файл под себя.
delay Если требуется начать запись не в момент старта приложения, а позже, то можно задать задержку данным параметром. Размерности значения: (s)econds, (m)inutes), (h)ours) или (d)ays. Пример: 5h . 0
duration Промежуток времени, в течение которого будет происходить запись. Размерности значения: (s)econds, (m)inutes, (h)ours или (d)ays. Пример: 300s . 0
filename Файл, куда будет записан результат записи. Этот файл будет записан при завершении записи или при остановке приложения, если установлен флаг dumponexit . Пример: «/home/user/My Recording.jfr».
disk Флаг, обозначающий, что запись должна сохраняться на диске по мере работы. Если этот флаг не установлен, то вся записанная информация будет копиться в памяти. false
maxage Максимальное время, в течение которого будут сохраняться записанные данные. Если в ходе записи данные скидываются на диск, то будет производиться ротация записанных файлов. Размерности значения: (s)econds, (m)inutes, (h)ours или (d)ays. Пример: 60m или 0 для безлимитной записи. 0
maxsize Максимальное количество данных, которое будет сохраняться при записи. Если в ходе записи данные скидываются на диск, то будет производиться ротация записанных файлов. Размерности значений: (k)B, (M)B или (G)B. Пример: 500M или 0 для безлимитной записи. 0
dumponexit Флаг, обозначающий необходимость сброса записанных данных на диск при завершении работы JVM. Включая аварийное завершение. false
path-to-gc-roots Если в настройках JFR включена запись информации о живых объектах, то данным флагом можно включить запись ещё и маршрутов по ссылкам до корней сборки мусора. false

XX:FlightRecorderOptions

Посмотреть список опций можно в исходном файле src/hotspot/share/jfr/recorder/service/jfrOptionSet.cpp .

Опция Описание Умолчание
repository Каталог, в который будут по блокам записываться данные JFR, если указана опция disk=true .
threadbuffersize Размер буфера под запись данных JFR, выделяемого для каждого потока. Размерности значений: (k)B, (M)B или (G)B. 8k
memorysize Суммарное количество памяти, используемое JFR для записи данных. 10m
globalbuffersize Размер единичного глобального буфера для записи данных JFR, куда перекладывается информация из локальных буферов потоков. Размерности значений: (k)B, (M)B или (G)B. 512k
numglobalbuffers Количество глобальных буферов для записи данных JFR. 20
maxchunksize Размер одного файлового блока в каталоге, заданном параметром repository . Размерности значений: (k)B, (M)B или (G)B. 12m
old-object-queue-size Максимальное количество наблюдаемых старых объектов. 256
samplethreads Включение / выключение снятия дампов потоков (дампы снимаются, только если соответствующие настройки включены в файле настроек). true
sampleprotection Включение внутренней защиты для хождения по стеку при снятии дампов потоков. false
stackdepth Максимальная глубина снимаемых стектрейсов (минимум 1, максимум 2048) 64
retransform Нужно ли инструментировать классы событий с использованием JVMTI? true

Примеры использования

Минимально возможный пример запуска Java с JFR выглядит так:

java -XX:StartFlightRecording -version

В результате в консоль будет выдано сообщение:

Started recording 1. No limit specified, using maxsize=250MB as default. Use jcmd 4787 JFR.dump name=1 filename=FILEPATH to copy recording data to file. openjdk version "1.8.0_275" OpenJDK Runtime Environment (build 1.8.0_275-b01) OpenJDK 64-Bit Server VM (build 25.275-b01, mixed mode)

Тут мы видим, что, помимо версии Java, запрашиваемой параметром -version , выведены ещё две строки:

  • сообщение о том, что JFR запущен и с каким идентификатором,
  • подсказка, как получить результаты работы JFR через утилиту jcmd .

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

Чтобы это исправить, можно задать дополнительные опции:

java -XX:StartFlightRecording=filename=recording.jfr -version

В результате по окончании работы приложения записанные данные будут сброшены в файл recording.jfr в текущем каталоге.

Вместо имени файла, можно указать имя каталога:

java -XX:StartFlightRecording=filename=./ -version

Тогда имя конкретного файла будет сгенерировано в момент записи, например: hotspot-pid-16330-id-1-2020_11_30_17_16_49.jfr . Это может быть полезно, если запускается много экземпляров JVM и не хочется перетирать данные предыдущих запусков.

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

В таком случае можно использовать набор настроек, позволяющих сбрасывать записанные данные по частям на диск:

java \ -XX:StartFlightRecording=disk=true,maxsize=10g,maxage=24h,filename=./recording.jfr \ -XX:FlightRecorderOptions=repository=./diagnostics/,maxchunksize=50m,stackdepth=1024 \ -jar application.jar

Прежде всего необходимо установить опцию disk=true , чтобы переключить JFR из режима записи в память в режим записи на диск.

Далее необходимо указать каталог, в который будет производиться запись: repository=./diagnostics/ . В этом каталоге начнут появляться файлы с фрагментами записи данных JFR. Размер каждого такого отдельного фрагмента задаётся опцией maxchunksize=50m , но не стоит обманываться. Этот параметр задаёт размер чистых данных JFR, но в каждый файл в конце будет дописываться ещё довольно много служебной информации, что в отдельных случаях может увеличить размер файла в разы. Зато каждый такой файл будет самодостаточным и доступным для независимого изучения.

По умолчанию файлы будут писаться до бесконечности, пока приложение не остановится. Но, если оно будет работать достаточно долго, место на диске может закончиться. Чтобы этого не произошло, можно ограничить количество сохраняемых данных по времени ( maxage=24h ) или объёму ( maxsize=10g ). Если один из этих параметров будет превышен, то старые файлы в каталоге ./diagnostics/ начнут стираться.

Ну и последний параметр из приведённого ранее примера — stackdepth=1024 . Он необходим для приложений с глубокими стеками вызовов, чтобы стек захватывался полностью.

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

Единственный существенный момент, который остался за скобками, — настройка снимаемых метрик JFR.

Файл конфигурации профиля JFR

По умолчанию с OpenJDK поставляется два преднастроенных профиля:

  • default.jfc (предназначен для мониторинга приложений на проде и дающий пенальти около 1% производительности),
  • profile.jfc (предназначен для профилирования приложений на проде и дающий пенальти около 2% производительности и значительно больший объём снимаемых данных (больший размер jfr файлов).

Оба профиля тщательно подобраны разработчиками OpenJDK и лежат в каталоге JAVA_HOME/jre/lib/jfr для OpenJDK 8 или JAVA_HOME/lib/jfr для OpenJDK 11+.

Активировать их можно с помощью опции, например settings=profile.jfc :

java -XX:StartFlightRecording=settings=profile.jfc -version

Во многих ситуациях этого может оказаться достаточно, но не во всех. Дело в том, что в этих двух профилях остаётся отключённым очень большое количество метрик, которые могут очень помочь в тех или иных ситуациях. Например, в default.jfc практически отключён сбор информации о потреблении памяти, хотя он не даёт заметного пенальти, но при этом весьма полезен для понимания того, куда расходуется память приложения.

Т.о., становится понятно, что для своих частных случаев имеет смысл формировать отдельный, специально заточенный под себя файл с настройками. Но править его вручную – дело неблагодарное. Поэтому рекомендуется воспользоваться инструментом Java Mission Control (JMC), который умеет не только просматривать файлы, записанные JFR, но и управлять профилями записи этих файлов.

Скачать дистрибутив JMC можно, например, с сайта компании BellSoft.

Если мы запустим JMC и выберем пункт меню Window -> Flight Recording Template Manager , то попадём в окно управления настройками профилей записи JFR:

По умолчанию тут будут представлены два профиля, поставляемые с OpenJDK. Но мы можем создать свой профиль, если сначала продублируем имеющийся профиль с помощью кнопки Duplicate , а потом выберем копию и нажмём кнопку Edit :

В открывшемся окне можно задать название профиля, его описание и настройки в упрощённом виде.

Но если упрощённых настроек не хватает, можно перейти в подробный режим, нажав кнопку Advanced :

Перейдя в такой режим настроек, можно включить дополнительные метрики для записи. Например, можно включить метрики:

  • ObjectAllocationInNewTLAB
  • ObjectAllocationOutsideTLAB

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

Немного о метриках TLAB

Важно понимать, как работает сбор данных метрик, чтобы правильно оценивать показываемые ими данные. JFR не пишет информацию о создании каждого объекта. Вместо этого, делается более хитрый ход: JVM не выделяет память напрямую из кучи, а отрезает от последней равные фрагменты и распределяет их по каждому потоку в отдельности. Локальное выделение объектов из таких локальных блоков происходит гораздо быстрее и без лишних блокировок. Так вот, в момент, когда очередной такой блок заканчивается, JFR записывает, при создании объекта какого класса это произошло, а также может записать стек, чтобы понять, в каком месте программы это произошло. Т.о., получается, что нельзя увидеть абсолютно всю информацию о выделяемых объектах, но при достаточно длительной работе приложения можно получить адекватную картину происходящего просто по закону больших чисел.

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

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

java -XX:StartFlightRecording=settings=/path/to/custom/profile.jfc -version

Управление JFR через командную строку (jcmd)

Но что делать, если приложение уже работает и надо запустить запись JFR? Или приостановить запись по какой-то причине? Или ещё что-то сделать?

Для этого можно воспользоваться штатной утилитой OpenJDK — jcmd. Например, запустить JFR можно следующей командой:

jcmd JFR.start duration=60s filename=myrecording.jfr settings=/path/to/custom/profile.jfc

Где — PID процесса JVM, в котором необходимо запустить JFR. В ответ будет выдана информация о том, что JFR запущено и куда именно будет записан файл с результатом:

19380: Started recording 2. The result will be written to: /media/data/java/OpenJDK/lmc7.1.1/myrecording.jfr

Данная команда запускает запись JFR на 60 секунд и указывает на то, чтобы записать результат в файл myrecording.jfr . Причём при таком указании файл будет записан по пути, локальному относительно не каталога запуска jcmd, а каталога, из которого была запущена JVM, в которой запускается JFR.

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

Также в ответе jcmd видна цифра 2 . Это имя записи. Если мы в дальнейшем захотим как-то повзаимодействовать с этой записью, нам необходимо будет использовать данное имя. Например, вот так можно остановить запись:

jcmd JFR.stop name=2

Утилита jcmd может выполнять несколько команд, связанных с JFR. Рассмотрим их по порядку.

JFR.start

Запускает запись JFR с заданным именем, аналогично параметру запуска JVM -XX:StartFlightRecording . Если имя явно не задано, оно генерируется автоматически и выводится в консоль.

Можно использовать следующие опции:

Опция Описание Умолчание
name Имя, которое может использоваться для идентификации запускаемой записи, например: «My Recording»
settings Имя или путь к конфигурации профиля записи. Имя может быть profile или default. См. JRE_HOME/lib/jfr
delay Задержка перед началом записи. Размерности значения: (s)econds, (m)inutes), (h)ours) или (d)ays, например: 5h. 0
duration Продолжительность записи. Размерности значения: (s)econds, (m)inutes, (h)ours или (d)ays, например: 300s. 0
filename Файл, куда следует внести результат записи, например: «/home/user/My Recording.jfr».
disk Запись должна вестись на диск, а не в памяти.
maxage Максимальный промежуток времени, в течение которого будут сохраняться на диске записываемые данные. Размерности значения: (s)econds, (m)inutes, (h)ours или (d)ays, например: 60m или 0 для отсутствия ограничения 0
maxsize Максимальное количество данных, сохраняемых при записи на диск. Размерности значения: (k)B, (M)B или (G)B, например: 500M или 0 для отсутствия ограничения. 0
dumponexit Сбросить записанные данные на диск, если JVM прекращает работу.
path-to-gc-roots Сохранять пути до корней GC у объектов. false

JFR.configure

Настраивает запись JFR аналогично параметру -XX:FlightRecorderOptions . Данная команда должна выполняться перед JFR.start , чтобы изменения вступили в силу.

Опция Описание Умолчание
repositorypath Путь к каталогу репозитория, куда будет производиться запись данных JFR по мере работы, например, «/path/to/repository». Имеет смысл, если при старте будет указана опция disk=true .
dumppath Путь, куда будет сброшен результат записи при завершении работы JVM. Включая аварийное завершение.
stackdepth Глубина захватываемого стека при записи стектрейсов. 64
globalbuffercount Количество глобальных буферов для записи данных JFR. 20
globalbuffersize Размер единичного глобального буфера для записи данных JFR, куда перекладывается информация из локальных буферов потоков. Размерности значений: (k)B, (M)B или (G)B. 512k
thread_buffer_size Размер буфера под запись данных JFR, выделяемого для каждого потока. Размерности значений: (k)B, (M)B или (G)B. 8k
memorysize Суммарное количество памяти, используемое JFR для записи данных. 10m
maxchunksize Размер одного файлового блока в каталоге, заданном параметром repository . Размерности значений: (k)B, (M)B или (G)B. 12m
samplethreads Включение / выключение снятия дампов потоков (дампы снимаются, только если соответствующие настройки включены в файле настроек). true

JFR.stop

Останавливает запись и сбрасывает записанные данные в файл, указанный при старте или непосредственно при вызове данной команды.

Опция Описание
name Имя записи, которую необходимо остановить
filename Файл, куда нужно записать результат записи, например: «/home/user/My Recording.jfr».

JFR.dump

Эта команда позволяет получить записанные данные, не останавливая саму запись. Ещё одной отличительной особенностью данной команды от JFR.stop является возможность сохранить не всю запись, а только интересующую её часть.

Опция Описание Умолчание
name Имя записи, от которой необходимо получить данные.
filename Файл, в который необходимо записать выгружаемые данные.
maxage Максимальный промежуток времени, который будет выгружен в файл. Размерности значения: (s)econds, (m)inutes, (h)ours или (d)ays, например: 60m или 0 для отсутствия ограничения. 0
maxsize Максимальное количество данных, которое будет выгружено в файл. Размерности значения: (k)B, (M)B или (G)B, например: 500M или 0 для отсутствия ограничения. 0
begin Значение времени, начиная с которого нужно выгрузить данные записи, например: 09:00, 21:35:00, 2018-06-03T18:12:56.827Z, 2018-06-03T20:13:46.832, -10m, -3h или -1d.
end Значение времени, до которого необходимо выгрузить данные записи, например: 09:00, 21:35:00, 2018-06-03T18:12:56.827Z, 2018-06-03T20:13:46.832, -10m, -3h или -1d.
path-to-gc-roots Сохранять пути до корней GC у объектов. false

JFR.check

Выводит информацию об активных записях JFR. Данная команда полезна, чтобы понять, запущена ли уже запись JFR в JVM и требуется ли её запускать или можно смотреть информацию в уже запущенной. А также чтобы убедиться, что записываются интересуемые события (метрики) JFR.

Опция Описание Умолчание
name Имя записи, для которой запрашивается информация. Если не указано, то будет выведена информация для всех активных записей.
verbose Вывести полную информацию по записываемым событиям JFR. false

Примеры использования

Разберём типовой сценарий использования jcmd для работы с JFR.

В качестве подопытного буду использовать запущенный Java Mission Control, который ранее использовался для формирования настроек записи.

Прежде всего посмотрим, не запущена ли уже запись JFR:

jcmd JFR.check
vektor@work$ jcmd 28534 JFR.check 28534: Recording 1: name=JMC_Default maxsize=100,0MB (running)

Видно, что уже запущена запись с именем JMC_Default .

Допустим, мне необходимо посмотреть, как расходуется куча JVM. Как понять, снимается данная информация в запущенной записи или нет? Для этого можно посмотреть подробную информацию и найти в ней настройки для jdk.ObjectAllocationInNewTLAB и jdk.ObjectAllocationOutsideTLAB . Выполняем:

jcmd JFR.check name=JMC_Default verbose=true

И получаем результат (сокращено для наглядности):

vektor@work$ jcmd 28534 JFR.check name=JMC_Default verbose=true 28534: Recording 1: name=JMC_Default maxsize=100,0MB (running) Flight Recording (jdk.ActiveRecording) [enabled=true] Recording Setting (jdk.ActiveSetting) . Allocation in new TLAB (jdk.ObjectAllocationInNewTLAB) [enabled=false,stackTrace=true] Allocation outside TLAB (jdk.ObjectAllocationOutsideTLAB) [enabled=false,stackTrace=true] . 

Видно, что интересующие меня метрики отключены. Но не беда: можно запустить отдельную запись в том же процессе JVM с интересующими меня настройками:

jcmd JFR.start \ name=memory_profile \ duration=10m \ filename=/path/to/recording.jfr \ settings=/path/to/memory_profile.jfc

И получим вывод:

vektor@work$ jcmd 28534 JFR.start name=memory_profile duration=10m filename=/path/to/recording.jfr settings=/path/to/memory_profile.jfc 28534: Started recording 4. The result will be written to: /path/to/recording.jfr

Видно, что запись успешно запустилась.

Также можно посмотреть список всех записей:

jcmd JFR.check

И убедиться, что наша запись работает параллельно с ранее запущенной:

vektor@work$ jcmd 28534 JFR.check 28534: Recording 1: name=JMC_Default maxsize=100,0MB (running) Recording 4: name=memory_profile duration=10m (running)

Через 10 минут мы получим файл с результатами записи, который готов к открытию и изучению в Java Mission Control.

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

jcmd 28534 JFR.configure repositorypath=/path/to/disk/cahce maxchunksize=20m

Таким образом мы зададим каталог, куда будет скидываться записываемая информация. Плюс мы устанавливаем размер одного файла. Т.е. данные будут писаться фрагментами по 20 Мб, каждый из которых будет самодостаточным, и его можно будет просматривать в JMC независимо от всех остальных файлов.

В результате выполнения команды увидим следующий ответ:

vektor@work$ jcmd 28534 JFR.configure repositorypath=/path/to/disk/cahce maxchunksize=20m 28534: Repository path: /path/to/disk/cahce/2020_12_03_16_58_40_28534 Max chunk size: 20,0 MB

Т.е. настройки установлены успешно. Теперь можно запускать непрерывную запись JFR:

jcmd 28534 JFR.start \ name=continuouse_record \ disk=true \ maxage=24h \ maxsize=10g \ settings=/path/to/memory_profile.jfc

Самым главным моментом в данном примере является включение записи на диск, вместо хранения данных в памяти. Также ограничиваем максимальное время хранения данных одними сутками, а размер – 10 Гб. Как минимум для того, чтобы диск не переполнился.

После этого в каталоге /path/to/disk/cahce/2020_12_03_16_58_40_28534 начнут появляться файлы с записанными данными. А при превышении ограничений по времени или размеру старые файлы будут стираться.

Теперь, если мы захотим посмотреть на поведение JVM в какой-то момент времени, можно открыть соответствующий файл в JMC. Однако это не слишком удобно, т.к. нас может интересовать отрезок времени, не совпадающий с границами записи в файлах. Для решения этой проблемы мы можем выгрузить именно интересующий нас фрагмент записи:

jcmd 28534 JFR.dump \ name=continuouse_record \ begin=-10m \ filename=/path/to/recording.jfr

В результате получим вывод:

vektor@work$ jcmd 28534 JFR.dump name=continuouse_record begin=-10m filename=/path/to/recording.jfr 28534: Dumped recording "continuouse_record", 13,1 MB written to: /path/to/recording.jfr

Таким образом мы сохранили в файл данные за последние 10 минут. При этом сама запись не остановилась, и, если с приложением произойдёт ещё что-то интересное, мы сможем вытащить информацию за этот промежуток времени и проанализировать, что же с приложением случилось, во всех деталях.

Заключение

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

За скобками осталось ещё очень много интересного, т. к. управлять записью можно ещё и программно. А также добавлять свои типы событий и метрик, а потом анализировать всю собранную информацию в Java Mission Control.

Но это всё уже темы для других статей.

А тем, кого интересует, как использовать JFR для анализа и оптимизации приложения, я рекомендую серию статей от Алексея Рагозина:

  • JDK Flight Recorder – a gem hidden in OpenJDK
  • Hunting down code hotspots with JDK Flight Recorder
  • Hunting down memory issues with JDK Flight Recorder

Оператор try with resources

Иногда в процессе работы Java-программа взаимодействует с объектами вне Java-машины. Например, с файлами на диске. Такие объекты принято называть внешними ресурсами. Внутренние ресурсы — это объекты, созданные внутри Java-машины.

Обычно взаимодействие происходит по такой схеме:

Оператор try with resources

Учет ресурсов

Операционная система ведет строгий учет доступных ресурсов, а также контролирует совместный доступ разных программ к ним. Например, если одна программа меняет какой-то файл, другая программа не может изменить (или удалить) этот файл. Это касается не только файлов, но на их примере понятнее всего.

У операционной системы есть функции (API), которые позволяют программе захватить какой-либо ресурс и/или освободить его. Если ресурс занят, с ним может работать только та программа, которая его захватила. Если ресурс свободен, любая программа может захватить его.

Представьте, что у вас в офисе есть общие кружки. Если кто-то взял кружку, другой уже не может взять ее. Но если ей попользовались, помыли и поставили на место, ее снова может брать кто угодно. Ну или места в метро или в маршрутке. Если место свободно — любой может его занять. Если место занято — им распоряжается тот, кто занял.

Захват внешних ресурсов

Каждый раз, когда ваша Java-программа начинает работать с каким-то файлом на диске, Java-машина запрашивает у операционной системы монопольный доступ к нему. Если ресурс свободен, его захватывает Java-машина.

Но после того, как вы закончили работать с файлом, этот ресурс (файл) нужно освободить: уведомить операционную систему, что он вам больше не нужен. Если вы этого не сделаете, ресурс будет продолжать числиться за вашей программой.

Для каждой запущенной программы операционная система ведет список занятых ресурсов. Если ваша программа превысит разрешенный ей лимит ресурсов, новые ресурсы операционная система вам уже не даст.

Хорошая новость в том, что если ваша программа завершилась, все ресурсы автоматически освобождаются (это делает сама операционная система).

Плохая же новость в том, что если вы пишете серверное приложение (а очень много серверных приложений пишутся на Java), ваш сервер должен работать днями, неделями, месяцами без остановки. И если вы в день открываете 100 файлов и не закрываете их, через пару недель ваше приложение исчерпает свой лимит и упадет. Не очень-то похоже на месяцы стабильной работы.

2. Метод close()

У классов, которые используют внешние ресурсы, есть специальный метод для их освобождения — close() .

Ниже приведем пример программы, которая что-то пишет в файл и закрывает его за собой – освобождает ресурсы операционной системы. Выглядит это примерно так:

String path = "c:\\projects\\log.txt"; FileOutputStream output = new FileOutputStream(path); output.write(1); output.close();

После работы с файлом (или другим внешним ресурсом) вы должны вызвать у объекта, связанного с внешним ресурсом, метод close() .

Исключения

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

Чтобы метод close() вызывался всегда, нужно обернуть наш код в блок try — catch — finally и добавить метод close() в блок finally . Выглядеть это будет примерно так:

try < FileOutputStream output = new FileOutputStream(path); output.write(1); output.close(); >catch (IOException e) < e.printStackTrace(); >finally < output.close(); >

Этот код не скомпилируется, т.к. переменная output объявлена внутри блока try <> , а значит, не видна в блоке finally .

FileOutputStream output = new FileOutputStream(path); try < output.write(1); output.close(); >catch (IOException e) < e.printStackTrace(); >finally < output.close(); >

Хорошо, но не будет работать, если ошибка возникла при создании объекта FileOutputStream , а это может произойти очень легко.

FileOutputStream output = null; try < output = new FileOutputStream(path); output.write(1); output.close(); > catch (IOException e) < e.printStackTrace(); >finally < output.close(); >

Все еще есть несколько замечаний. Во-первых, если во время создания объекта FileOutputStream возникнет ошибка, переменная output будет null, и этот факт нужно учесть в блоке finally .

Во-вторых, метод close() вызывается в блоке finally всегда, а значит, он не нужен в блоке try . Финальный код будет выглядеть так:

FileOutputStream output = null; try < output = new FileOutputStream(path); output.write(1); > catch (IOException e) < e.printStackTrace(); >finally < if (output != null) output.close(); >

Даже если не считать блок catch , который можно опустить, наш код из трех строк превратился в 10. Хотя по сути мы только открыли файл и записали в него 1. Немного громоздко, не находите ли?

3. try -with-resources

Создатели Java и тут решили нам подсыпать немного синтаксического сахарку. Начиная с 7-й версии Java, в ней появился новый оператор try -with-resources ( try с ресурсами).

Он создан как раз для того, чтобы решать проблему с обязательным вызовом метода close() . В общем случае выглядит он довольно просто:

try (Класс имя = new Класс()) < Код, который работает с переменной имя >

Это еще одна разновидность оператора try . После ключевого слова try нужно добавить круглые скобки, а внутри них — создать объекты с внешними ресурсами. Для объекта, указанного в круглых скобках, компилятор сам добавит секцию finally и вызов метода close() .

Ниже написано два эквивалентных примера:

FileOutputStream output = null; try < output = new FileOutputStream(path); output.write(1); > finally < if (output != null) output.close(); >
try(FileOutputStream output = new FileOutputStream(path)) < output.write(1); >

Код с использованием try -with-resources значительно короче и легче читается. А чем меньше кода, тем меньше шансов сделать опечатку или ошибку.

Кстати, у оператора try -with-resources можно дописывать блоки catch и finally . А можно и не добавлять, если в них нет необходимости.

4. Несколько переменных одновременно

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

На этот случай оператор try -with-resources разрешает создавать в нем не один объект, а несколько. Код создания объектов должен разделяться точкой с запятой. Общий вид такой команды:

try (Класс имя = new Класс(); Класс2 имя2 = new Класс2()) < Код, который работает с переменной имя и имя2 >

Пример копирования файлов:

String src = "c:\\projects\\log.txt"; String dest = "c:\\projects\\copy.txt"; FileInputStream input = null; FileOutputStream output = null; try < input = new FileInputStream(src); output = new FileOutputStream(dest); byte[] buffer = input.readAllBytes(); output.write(buffer); > finally < if (input != null) input.close(); if (output != null) output.close(); >
String src = "c:\\projects\\log.txt"; String dest = "c:\\projects\\copy.txt"; try(FileInputStream input = new FileInputStream(src); FileOutputStream output = new FileOutputStream(dest)) < byte[] buffer = input.readAllBytes(); output.write(buffer); >

В общем, что тут скажешь: отличная это вещь — try -with-resources

5. Интерфейс AutoCloseable

Но и это еще не все. Внимательный читатель сразу начнет искать подвох границы применимости данного оператора.

А как будет работать оператор try -with-resources, если у класса нет метода close() ? Ну, допустим, тогда ничего не вызовется. Нет метода, нет проблем.

А как будет работать try -with-resources если у класса есть несколько методов close() ? И им нужно передавать параметры? И у класса нет метода close() без параметров?

Надеюсь, вы действительно задали себе эти вопросы, и возможно не только их.

Для того чтобы таких вопросов не было, создатели Java придумали специальный класс (интерфейс) AutoCloseable у которого только один метод – close() без параметров.

А также добавили ограничение, что в качестве ресурсов в try -with-resources можно передавать только объекты классов унаследованных от AutoCloseable . Таким образом у этих объектов всегда будет метод close() без параметров.

Кстати, как вы думаете, можно ли в качестве ресурса передать в try -with-resources объект, чей класс имеет метод close() без параметров, но который не унаследован от AutoCloseable ?

Плохая новость: правильный ответ – нет, классы обязательно должны реализовывать интерфейс AutoCloseable .

Хорошая новость: в Java очень много классов реализовывают этот интерфейс, так что с большой долей вероятности все будет работать как надо.

Узнать путь к файлу, который сейчас выполняется

Что-то вроде установщика. В папке лежат файлы и .jar файл. Неизвестно где будет папка. В .jar нужно из этой папки скопировать файлы. Как указать путь к ним для команды files.copy() ? В cmd можно без указания пути обращаться к таким файлам. Так как по умолчанию файлы ищутся в папке, откуда запущен скрипт.

Отслеживать

543 5 5 серебряных знаков 14 14 бронзовых знаков

задан 21 дек 2020 в 5:13

93 8 8 бронзовых знаков

1 ответ 1

Сортировка: Сброс на вариант по умолчанию

Например, вариант из ответа @Benny Neugebauer:

//подставить название своего класса для получения источника CodeSource codeSource = YourMainClass.class.getProtectionDomain().getCodeSource(); //Путь к JAR File jarFile = new File(codeSource.getLocation().toURI().getPath()); //Путь к папке String jarDir = jarFile.getParentFile().getPath(); 

Отслеживать

ответ дан 21 дек 2020 в 6:09

default locale default locale

18.5k 4 4 золотых знака 31 31 серебряный знак 45 45 бронзовых знаков

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *