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, который использовался в момент сбоя, доступен в среде анализа после сбоя. Это разумное допущение, поскольку доступ к правильному исполняемому файлу требуется и для существующих методов анализа сбоев.