openjdk.ruOpenJDK на русском

JEP 528: Post-Mortem Crash Analysis with jcmd

Посмертный анализ сбоев с помощью jcmd

ОтветственныйKevin Walls
ТипFeature
ОбластьJDK
СтатусCandidate
Выпускtbd
Компонентcore-svc / tools
Обсуждениеserviceability dash dev at openjdk dot org
ТрудоёмкостьM
ДлительностьM
РецензентыAlan Bateman, Alex Buckley
ОдобренMikael Vidstedt
Создан2024/03/18 12:00
Обновлён2026/06/10 15:14
Задача8328351

Аннотация

Инструмент jcmd служит для мониторинга и диагностики работающей HotSpot JVM. Мы расширяем jcmd, чтобы с его помощью можно было диагностировать и JVM, завершившуюся сбоем. Так диагностика будет одинаковой как для работающей JVM, так и после сбоя.

Цели

  • Сделать диагностику JVM после сбоя такой же привычной и продуктивной, как диагностику работающих JVM.

  • Обеспечить посмертную диагностику в Linux и Windows.

  • Снизить будущие затраты на сопровождение JDK: сосредоточить работу над serviceability на jcmd, а не на других инструментах и компонентах, таких как jhsdb и лежащий в его основе Serviceability Agent.

Что не является целью

  • Поддержка всех диагностических команд jcmd в посмертной среде не является целью.

  • Запуск и отладка Java-кода в посмертной среде не являются целью.

  • Обеспечение посмертной диагностики во всех поддерживаемых операционных системах не является целью.

  • Удаление устаревших инструментов и компонентов serviceability, таких как jhsdb и Serviceability Agent, на данном этапе не является целью.

Мотивация

Serviceability — это возможность отслеживать, наблюдать, отлаживать и диагностировать приложение. Инструменты мониторинга и наблюдения позволяют подключиться к работающей JVM и исследовать приложение. Это касается как кода приложения, например загруженных классов и методов, скомпилированных JIT-компилятором, так и его состояния, например Java-кучи и стеков Java-потоков и нативных потоков. Инструменты JDK, такие как jstack и jmap, создают дампы кучи и дампы потоков работающей JVM, а инструменты вроде JDK Mission Control позволяют наглядно просматривать использование памяти и потоки. Если инструмент подключается к JVM по протоколу JMX, вы также можете диагностировать приложение, например включив более подробное журналирование отдельных подсистем.

В крайних случаях JVM может неожиданно завершиться так, что эти инструменты не смогут это отследить. Причиной могут быть ошибки в нативном коде приложения или библиотек либо ошибки в самой JVM. (Это не то же самое, что завершение из-за неперехваченного исключения, например NullPointerException или OutOfMemoryError.)

При завершении HotSpot JVM создаёт файл отчёта о сбое (hs_err_pidXXX.log) со сведениями о сбое и о состоянии приложения, например трассировкой стека сбойного потока и списком загруженных библиотек. Операционная система также сохраняет память процесса JVM в файл, называемый core dump. Отчёты о сбоях и файлы core dump можно использовать для посмертного анализа, чтобы глубже понять, что пошло не так, и определить шаги к устранению проблемы.

К сожалению, с инструментами, доступными для посмертного анализа core dump JVM, есть проблемы:

  • Работать с нативными отладчиками, такими как gdb, неудобно: они не умеют интерпретировать структуры данных JVM в core dump и показывать состояние приложения на уровне Java. Например, если вы определили, что Java-объект начинается по определённому адресу в памяти, то даже для такой простой вещи, как класс этого объекта, приходится вручную декодировать слова в заголовке объекта. Скрипты отладчика помогают автоматизировать декодирование структур данных JVM в core dump, но эта работа по-прежнему чревата ошибками, а скрипты требуют постоянного сопровождения, поскольку расположение заголовков объектов со временем меняется.

  • Инструмент jhsdb, появившийся в JDK 9, может открыть core dump и интерпретировать структуры данных JVM. Он использует внутренний механизм HotSpot, известный как Serviceability Agent (SA). К сожалению, кодовая база SA хрупкая и устаревшая: она требует постоянного сопровождения по мере развития JVM и серьёзной работы для поддержки новых возможностей JVM. (Несмотря на название, Serviceability Agent не является Java agent, то есть компонентом, который может изменять код работающего приложения.)

jcmd, появившийся в JDK 7, — лёгкий инструмент для диагностики JVM. Он может подключаться к работающей JVM через Attach API и показывать сведения о приложении на уровне Java. Он предлагает более 50 команд: для вывода списка Java-потоков, подробных сведений об использовании памяти, проверки состояния сборщика мусора и так далее. Однако jcmd может подключаться только к работающим процессам. Учитывая его гибкость и популярность, было бы полезно, если бы jcmd можно было использовать и для посмертного анализа core dump. Так диагностика была бы одинаковой как для работающих процессов, так и после сбоя.

Описание

Мы расширяем jcmd для поддержки посмертного анализа: данные из core dump используются, чтобы воссоздать образ памяти JVM на момент сбоя, а код из исполняемого файла JVM выполняется, чтобы интерпретировать структуры данных в этом образе. Благодаря этой технике оживления диагностические команды jcmd работают точно так же, как в работающей JVM, без изменений в самих командах или их реализации.

Например, если в результате сбоя JVM получен core dump core.1234, то запуск:

$ jcmd core.1234 Thread.print

выдаст такой же вывод, как при подключении jcmd к работающей JVM:

Opening dump file 'core.1234'...
2025-04-01 14:17:18
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25-internal-LTS-2025-03-30-1738352.name... mixed mode, sharing):
...
"Thread-0" #34 [1183517] prio=5 os_prio=0 cpu=0.99ms elapsed=0.07s tid=0x00007ff8fc208cc0 ...
   java.lang.Thread.State: RUNNABLE
Thread: 0x00007ff8fc208cc0  [0x120f1d] State: _at_safepoint _at_poll_safepoint 0
   JavaThread state: _thread_blocked
        at ThreadsMem$1.run(ThreadsMem.java:25)
        - locked <0x00000000fe300c98> (a java.lang.Object)
        at java.lang.Thread.runWith(java.base@25-internal/Thread.java:1460)
        at java.lang.Thread.run(java.base@25-internal/Thread.java:1447)
        ...
...

jcmd в посмертной среде

Сейчас jcmd поддерживает 57 команд для работающей JVM, из них 26 применимы и доступны в посмертной среде:

Compiler.CodeHeap_Analytics    Compiler.codecache    Compiler.codelist    Compiler.directives_print
Compiler.memory                Compiler.perfmap      Compiler.queue

GC.class_histogram             GC.heap_dump          GC.heap_info

JVMTI.data_dump

Thread.print

VM.class_hierarchy             VM.classes            VM.classloader_stats VM.classloaders
VM.command_line                VM.events             VM.flags             VM.metaspace
VM.native_memory               VM.stringtable        VM.symboltable       VM.systemdictionary
VM.version

help

Посмертная среда должна иметь ту же операционную систему и архитектуру процессора, что и среда, в которой произошёл сбой JVM.

Доступ к рабочим серверам, на которых произошёл сбой JVM, часто затруднён, поэтому core dump обычно переносят на машины разработчиков для анализа. На таких машинах, как правило, установлены более новые JDK, чем на рабочих серверах. Чтобы упростить анализ, инструмент jcmd не обязательно должен быть из того же JDK, что и JVM, в которой произошёл сбой. Инструмент jcmd из одного выпуска JDK может оживлять core dump из другого выпуска JDK, если доступен исполняемый файл JVM этого другого выпуска. Другой выпуск может быть старше или новее выпуска, в котором запущен jcmd, если оба выпуска не ниже JDK NN. При запуске jcmd путь к исполняемому файлу JVM указывается новым параметром -L:

$ jcmd -L $TRANSPORTED_FILES/libjvm.so core.1234 Thread.print

В JDK NN jcmd может принимать в качестве аргумента либо имя Java-класса, либо имя файла core dump. Поскольку имя файла core dump может быть похоже на имя класса, новый параметр -c указывает, что аргумент действительно является core dump:

$ jcmd -c MyApp GC.heap_dump

Оживление core dump

Чтобы оживить экземпляр JVM из core dump, jcmd создаёт подпроцесс, чтобы у оживлённого экземпляра было собственное адресное пространство, отдельное от адресного пространства JVM, в которой запущен jcmd. Он заполняет это адресное пространство, отображая core dump в память, чтобы воссоздать внутренние структуры данных JVM, Java-кучу и стеки нативных потоков — всё по исходным адресам памяти, чтобы указатели оставались действительными. jcmd также загружает исполняемый файл JVM (libjvm.so) по его исходному адресу в памяти.

Оживлённый экземпляр JVM не является работающим в том же смысле, что во время выполнения. Java-код выполнить нельзя, и сборка мусора не происходит. Тем не менее экземпляр достаточно полон, чтобы jcmd мог интерпретировать структуры данных в оживлённом экземпляре, вызывая в нём нативные функции JVM — те же самые функции, которые он вызывает у работающего экземпляра через Attach API. При таком подходе диагностический код jcmd не зависит от того, жива наблюдаемая JVM или нет: в обоих случаях диагностика вызывает одни и те же нативные функции. Поэтому не нужен новый код, чтобы, например, исследовать объект, получить дамп кучи или получить кадры стека потока.

Кроме исполняемого файла JVM, загружать какие-либо нативные библиотеки из процесса, завершившегося сбоем, не нужно. Поэтому core dump можно диагностировать после переноса на другую машину, где тех же нативных библиотек может не быть. Для посмертного анализа jcmd нужны только core dump и исполняемый файл JVM, в которой произошёл сбой.

Дальнейшая работа

  • В будущем мы планируем поддержать посмертную диагностику с помощью jcmd в MacOS в дополнение к Linux и Windows.

  • Мы ожидаем внести в jcmd дальнейшие улучшения, помогающие диагностике как в работающей, так и в посмертной среде. Два примера — новые команды для исследования произвольных Java-объектов и для извлечения определений Java-классов (дамп классов). Мы также ожидаем доработать некоторые существующие команды, например VM.uptime, чтобы они работали в обеих средах.

  • Некоторые существующие диагностические команды реализованы на Java, а не в нативном коде. Это значит, что они несовместимы с оживлением процесса и не могут использоваться в посмертной среде. Из этих команд некоторые полезны только в работающей среде, например ManagementAgent.start и JFR.start. Другие же могли бы быть полезны и после сбоя: например, Thread.dump_to_file формирует список Virtual Threads (виртуальные потоки) в виде текста JSON. Мы можем изучить такие команды, чтобы понять, можно ли заставить их работать в посмертной среде.

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

Альтернативы

  • Вкладываться в улучшение Serviceability Agent (SA).

    SA написан на Java. Он использует нативную библиотеку (libsaproc), которая возвращает содержимое сырой памяти работающего процесса или core dump. Благодаря этому код SA не зависит от того, жива наблюдаемая JVM или нет, но это также значит, что SA должен превращать массивы байтов, возвращаемые libsaproc, в экземпляры Java-классов, моделирующих потоки, кадры стека, блокировки и так далее. Такая интерпретация трудоёмка и запутанна: от сопровождающих SA требуется, например, знать, как каждый сборщик мусора размещает Java-объекты в куче. Код SA приходится постоянно обновлять по мере развития JVM. Наконец, он дублирует функциональность огромных объёмов нативного кода JVM, управляющего структурами данных времени выполнения.

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

    SA пошёл на высокую сложность реализации, чтобы предоставить богатый Java API. Однако из-за быстрого темпа разработки JVM сопровождение этой сложности стало дорогим, и функциональность API пострадала. К тому же богатая функциональность Java API в SA избыточна для диагностики JVM. Вместо подхода SA «высокая стоимость, богатые возможности» jcmd с оживлением процесса использует подход «низкая стоимость, основные возможности». Поскольку стоимость низкая, его можно поддерживать в долгосрочной перспективе.

  • По-прежнему полагаться на нативные отладчики, такие как gdb.

    Нативные отладчики позволяют проводить низкоуровневую диагностику JVM и останутся важной частью диагностики JDK. Однако из-за технических усилий, необходимых для вывода Java-объектов в удобочитаемом виде, они плохо подходят как альтернатива расширенному jcmd.

Риски и допущения

  • Риск возможности использовать jcmd после сбоя состоит в том, что core dump с конфиденциальной информацией могут переноситься для анализа в незащищённые среды. Однако сегодня с jhsdb и SA ситуация такая же, поэтому нового риска для безопасности нет.

  • Мы предполагаем, что исполняемый файл JVM, который использовался в момент сбоя, доступен в среде анализа после сбоя. Это разумное допущение, поскольку доступ к правильному исполняемому файлу требуется и для существующих методов анализа сбоев.