Говоря о модуле JVMTI в JPDA

Java задняя часть Debug API

0 Предисловие

предыдущий раздел"Использование и принцип функции инструмента Java"В статье при объяснении использования Instrument кратко упоминается концепция JVMTI, которая может быть незнакома многим мелким партнерам.Хотя использование Java Instrument в принципе не представляет проблемы, принцип реализации Instrument на основе JVMTI все еще находится в стадии разработки. хаотичное состояние. Таким образом, основное внимание в этом разделе уделяется объяснению JVMTI, как и в названии.

Но поскольку JVMTI — это лишь небольшой модуль во всей системе JVM JPDA, чтобы дать всем четкое представление о целом, начнем с системы JPDA.

1 Введение в JPDA

Все программисты будут сталкиваться с ошибками.Для ошибок времени выполнения нам часто нужны какие-то методы для наблюдения и тестирования среды во время выполнения. Для квалифицированного разработчика самым основным навыком является овладение навыками отладки языка в различных IDE.

Intellij IDEA предоставляет очень полный и простой отладчик, как показано ниже:

Intellij IDEA 调试界面

Иногда даже без графического интерфейса, предоставляемого IDE, используйте инструмент jdb, поставляемый с JDK, для отладки вашей Java-программы в виде текстовых команд.. Эти различные отладчикиПоддержка локальной и удаленной отладки программ, так как они разработаны? Какая связь существует между ними?

Мы должны упомянуть систему отладки Java - JPDA, которая является для нас каналом доступа к виртуальной машине и проверки рабочего состояния виртуальной машины, набора инструментов.

Все Java-программы запускаются на виртуальной машине Java, мы хотим отлаживать Java-программы,По сути, необходимо запросить текущее состояние состояния выполнения у виртуальной машины Java, выдать определенные инструкции виртуальной машине, установить какие-то обратные вызовы и т. д., то система отладки Java - JPDA,Это набор инструментов и интерфейсов для отладки виртуальных машин..

Как следует из названия, эта система предоставляет разработчикамНабор API для отладки Java-программ, набор интерфейсов и протоколов для разработки средств отладки Java.

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

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

1.1 Модуль JPDA

JPDA определяет полную и независимую систему, которая состоит из трех относительно независимых уровней и определяет режим взаимодействия между ними или определяет интерфейс их связи.

Три уровня от низкого к высокомуИнтерфейс Java Virtual Machine Tool (JVMTI), протокол отладки Java (JDWP) и интерфейс отладки Java (JDI). Эти три модуля разлагают процесс отладки на несколько естественных понятий:Отладчик и отлаживаемый и коммуникатор между ними.

Отладчик (JVMTI): запустить на виртуальной машине Java, которую мы хотим отлаживать,Он может отслеживать информацию о текущей виртуальной машине через стандартный интерфейс JVMTI.;

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

Промежуточный коммуникатор (JDWP):Между отладчиком и отлаживаемым отладчиком команды отладки и результаты отладки передаются по протоколу связи JDWP.; Все команды инкапсулируются в пакет команд JDWP и отправляются отлаживаемой программе через транспортный уровень. После того, как отлаживаемая программа получает пакет команд JDWP, она анализирует команду и преобразует ее в вызов JVMTI для выполнения в отладчике; аналогично, результаты запуска JVMTI, отформатированные в пакеты JDWP, отправленные в отладчик и возвращенные в вызовы JDI. А разработчики отладчиков получают данные через JDI и выдают инструкции;

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

JPDA 模块层次

Конечно, вместо использования всех трех уровней разработчики могутРазработайте собственное приложение на основе одного из слоев. Например, полностью возможно разработать средство отладки только через функцию JVMTI, вместо использования JDWP и JDI, и только используя собственный коммуникационный и командный интерфейс. Конечно, если нет особых потребностей, использование существующей реализации позволит делать больше с меньшими затратами и не изобретать велосипед.

Ниже мы объясним три компонента JPDA отдельно:

  1. Интерфейс виртуальных машин Java (JVMTI)

    JVMTI (Java Virtual Machine Tool Interface) относится к интерфейсу Java Virtual Machine Tool, которыйНабор собственных интерфейсов, предоставляемых непосредственно виртуальной машиной., лежащий в основе всей системы JPDA, и все функции отладки по существу должны обеспечиваться JVMTI. С помощью этих интерфейсов разработчики не только отлаживают Java-программы, работающие на виртуальной машине, но иПросматривайте их рабочее состояние, устанавливайте функции обратного вызова, управляйте определенными переменными среды.для оптимизации работы программы.

  2. Проводной протокол отладки Java (JDWP)

    JDWP (Java Debug Wire Protocol) — протокол коммуникационного взаимодействия, разработанный для отладки Java.Он определяет формат информации, передаваемой между отладчиком и отлаживаемой программой..

    В системе JPDA формат интерактивных данных между процессом внешнего отладчика (отладчика) и процессом внутреннего (внутреннего) отладчика (отладчика) описывается JDWP.Он подробно и полностью определяет команду запроса, данные ответа и код ошибки., что обеспечивает бесперебойную связь между JVMTI и JDI интерфейсной и серверной части.

    Например: в реализации, предоставленной Sun, он предоставляет файл библиотеки динамической компоновки с именем jdwp.dll (jdwp.so),Этот файл динамической библиотеки реализует агент, который отвечает за синтаксический анализ запроса или команды, отправленных интерфейсом, преобразование его в вызов JVMTI, а затем инкапсуляцию возвращаемого значения функции JVMTI в виде данных JDWP и отправку его обратно на сервер. конец.

    Также здесь следует отметить, чтоСам JDWP не включает в себя реализацию транспортного уровня, транспортный уровень нужно реализовывать самостоятельно,ноJDWP включает строгие определения для взаимодействия с транспортным уровнем.,то есть,Хотя соглашение JDWP не оговаривает, отправляем ли мы товары EMS или экспресс-доставкой, оно определяет способ размещения отправляемых нами товаров.. В JDK, предоставленном Sun, на транспортном уровне он предоставляет метод сокета и метод общей памяти в Windows. Конечно,Сам транспортный уровень — это не что иное, как режим межпроцессного взаимодействия и режим удаленного взаимодействия на локальной машине, и он тоже может быть реализован по стандарту JDWP..

  3. Интерфейс отладки Java (JDI)

    JDI (интерфейс отладки Java) — это интерфейс самого высокого уровня среди трех модулей.В большинстве JDK он реализован на языке Java.. JDI состоит из интерфейсов, определенных для внешнего интерфейса, с помощью которых разработчики средств отладки могут удаленно управлять выполнением отлаживаемой программы на внутренней виртуальной машине через отладчик на внешней виртуальной машине.JDI может не только помочь разработчикам форматировать данные JDWP, но и предоставить услуги оптимизации, такие как очереди и кэши для передачи данных JDWP.. Теоретически разработчикам достаточно использовать JDWP и JVMTI для поддержки удаленной кросс-платформенной отладки, но непосредственное написание программ JDWP отнимает много времени, трудоемко и неэффективно.Таким образом, внедрение слоя JDI на основе Java упрощает работу и повышает эффективность разработки и отладки программ разработчиками..

JPDA 层次比较

1.2 Реализация JPDA

Каждая виртуальная машина должна реализовывать интерфейс JVMTI, но сами JDWP и JDI неотделимы от виртуальной машины.Эти три слоя связаны через интерфейсы и протоколы взаимодействия, определенные стандартом, поэтому их можно заменять или заменять независимо друг от друга, но это не повлияет на разработку и использование общих средств отладки.. Поэтому можно разрабатывать и использовать собственные реализации интерфейсов JDWP и JDI.

Стандартная версия Java Software Development Kit (SDK) обеспечивает три уровня стандартной реализации JPDA., на самом деле разработчики средств отладки могут выбирать из многих других реализаций с открытым исходным кодом, например, Apache Harmony предоставляет реализацию JDWP. Что касается JDI, мы можем найти его полную реализацию в org.eclipse.jdt.debug, подпроекте Eclipse (который Harmony также использует как часть своей библиотеки классов J2SE). Благодаря стандартным протоколам инструменты отладки Eclipse IDE могут полностью работать в среде Harmony.

2 Введение в JVMTI

JVMTI (инструментальный интерфейс JVM)Собственный программный интерфейс, предоставляемый виртуальной машиной Java., альтернатива JVMPI (интерфейс профилировщика виртуальной машины Java) и JVMDI (интерфейс отладки виртуальной машины Java).

Из альтернативной трассировки этого API,JVMTI предоставляет интерфейсы, которые можно использовать для отладки и профилирования.; При этом в Java 5/6 добавлен еще и интерфейс JVMTIМониторинг, анализ потоков и анализ покрытияи другие функции. Именно благодаря мощности JVMTI он является основой для реализации отладчика Java, а также других инструментов тестирования и анализа во время выполнения Java.

JVMTI — это набор собственных интерфейсов кода, которые позволяют разработчикам напрямую работать с C/C++ и JNI..

Итак, как же разработчики используют интерфейс, предоставляемый JVMTI? По факту,Как правило, JVMTI используется путем создания агента, представлением которого является динамически подключаемая библиотека, написанная на языке c/c++..

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

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

2.1 Рабочий процесс агента

2.1.1 Запуск

JVMTI имеет два метода запуска: первый — автоматическая загрузка разделяемой библиотеки при запуске процесса Java, далее именуемогоЗагружать при запуске. Другой способ заключается в том, что среда выполнения Java динамически загружается через API прикрепления, далее именуемыйБег.

Загружать при запуске, передав специальную опцию при запуске командной строки Java следующим образом:

  1. java -agentlib:= SampleУведомление,Путь к общей библиотеке здесь — это путь к переменной среды.Например, java -agentlib:foo=opt1,opt2, при запуске java загрузит foo.so или foo.dll по пути, определенному LD_LIBRARY_PATH в linux или переменной среды PATH в Windows, и выдаст исключение, если оно не найден.

  2. java -agentpath:= SampleЭтоЗагрузить общую библиотеку с абсолютным путем, например java -agentpath:/home/admin/agentlib/foo.so=opt1,opt2

Загружать при запуске, на ранних этапах инициализации виртуальной машины, на данный момент:

  1. Все классы Java не инициализированы;
  2. Не все экземпляры объектов Java созданы;
  3. Таким образом, код Java не выполняется;

Но в это время мы уже можем:

  1. Манипулировать параметром возможностей JVMTI;
  2. использовать системные параметры;

После загрузки динамической библиотеки виртуальная машина сначала ищет функцию входа агента:

JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *vm, char *options, void *reserved)

В этой функции Agent_OnLoad виртуальная машина передает три параметра:

  1. JavaVM *vm:Контекст JVM,Через JavaVM можно получить указатель на JVMTI, и получить возможность использовать функции JVMTI;
  2. char *options:Внешне передаваемые параметры, такие как opt1, opt2, приведенные в приведенном выше примере,это просто строка;
  3. void *reserved: зарезервированный параметр, не обращайте на него внимания;

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

import java.io.IOException;
import com.sun.tools.attach.VirtualMachine;

public class VMAttacher {
    public static void main(String[] args) throws Exception {
         // args[0]为java进程id
         VirtualMachine virtualMachine = com.sun.tools.attach.VirtualMachine.attach(args[0]);
         // args[1]为共享库路径,args[2]为传递给agent的参数
         virtualMachine.loadAgentPath(args[1], args[2]);
         virtualMachine.detach();
    }
}

Attach API находится в $JAVA_HOME/lib/tools.jar, поэтому при компиляции вам нужно поместить этот jar в путь к классам. Например:

javac -cp $JAVA_HOME/lib/tools.jar VMAttacher.java pid /home/admin/agentlib/foo.so opt1,opt2

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

JNIEXPORT jint JNICALL Agent_OnAttach(JavaVM* vm, char *options, void *reserved);

2.1.2 Удаление

наконец-то,Когда агент завершает задачу или когда виртуальная машина выключается, виртуальная машина вызывает метод, аналогичный деструктору класса, для завершения окончательной задачи очистки.:

JNIEXPORT void JNICALL Agent_OnUnload(JavaVM *vm)

2.2 Среда JVMTI и обработка ошибок

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

2.2.1 Среда JVMTI

работаяjvmtiCapabilitiesДля запроса, добавления и изменения параметров среды JVMTI. стандартныйjvmtiCapabilitiesОпределяет ряд функций виртуальной машины, таких как:

  1. can_redefine_any_class: определяет, поддерживает ли виртуальная машина переопределение классов;
  2. can_retransform_classes: определяет, поддерживает ли виртуальная машина изменение определения класса во время работы;

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

// 取得 jvmtiCapabilities 指针
err = (*jvmti)->GetCapabilities(jvmti, &capa);
if (err == JVMTI_ERROR_NONE) {
    // 查看是否支持重定义类 
    if (capa.can_redefine_any_class) { ... }
} 

Кроме того, виртуальная машина имеет некоторые свои функции и не запускается в начале, затемТакже возможно добавлять или изменять jvmtiCapabilities, но разные виртуальные машины обрабатывают эту функцию по-разному.Большинство виртуальных машин позволяют добавлять и изменять, но есть определенные ограничения,Например:Поддерживается только тогда, когда сделан Agent_OnLoad, то есть при запуске виртуальной машины он в какой-то степени отражает архитектуру самой виртуальной машины. Разработчики могут включить все функции при загрузке агента, не принимая во внимание производительность и объем используемой памяти агента:

// 取得所有可用的功能
err = (*jvmti)->GetPotentialCapabilities(jvmti, &capa);
if (err == JVMTI_ERROR_NONE) { 
    err = (*jvmti)->AddCapabilities(jvmti, &capa); 
    ... 
}

Наконец, следует отметить, что вызовы функций JVMTI имеют свое время.То есть конкретная функция может быть вызвана только в определенном состоянии виртуальной машины..

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

2.2.2 Обработка ошибок JVMTI

JVMTI следует основному методу обработки ошибок,то есть уведомить текущую ошибку с возвращенным кодом ошибки, почти все вызовы функций JVMTI имеют следующий шаблон:

jvmtiError err = jvmti->someJVMTImethod (somePara … );

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

2.3 Основные функции JVMTI

JVMTI очень многофункциональна, в том числеПоток, память/куча/стек, класс/метод/переменная, обработка события/таймера и т. д. в виртуальной машинеСуществует более 20 типов функций, которые можно условно разделить на 4 категории с точки зрения функций следующим образом:

  1. Heap: получить всю информацию о классе, информацию об объекте, отношение ссылок на объекты, начало/конец полного GC, события повторного использования объектов и т. д.;
  2. нить и стек: Получить всю информацию о потоке, информацию о группе потоков, поток управления (запуск, приостановка, возобновление, прерывание...), Монитор потока (блокировка), получение стека потока, управление всплывающим окном, принудительный возврат метода, локальные переменные стека метода и т. д. .;
  3. Метаинформация о классе, объекте, методе и поле: информация о классе, таблица символов, таблица методов, класс переопределения (горячая замена), класс повторного преобразования, информация об объекте, информация о полях, информация о методе и т. д.;
  4. Инструменты: потребление процессора потоком, модификация пути загрузчика классов, получение системных атрибутов и т. д.;

2.3.1 Обработка событий и функции обратного вызова

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

Поэтому функции этой части очень просты.Текущая версия JVMTI предоставляет обратные вызовы для многих событий, в том числеИнициализация виртуальной машины, запуск, завершение, загрузка класса, вход и выход метода, начало и завершение потока и т. д.. Если вы хотите обрабатывать эти события,Вам нужно сначала написать функцию для события, а затем указать соответствующий указатель функции в структуре jvmtiEventCallbacks.

Например: нас интересует запуск потока и мы пишем функцию HandleThreadStart, затем нам нужно добавить ее в функцию Agent_OnLoad:

jvmtiEventCallbacks eventCallBacks; 
// 初始化
memset(&ecbs, 0, sizeof(ecbs));
// 设置函数指针
eventCallBacks.ThreadStart = &HandleThreadStart;
...

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

jvmti->SetEventCallbacks(eventCallBacks, sizeof(eventCallBacks));

При настройке callback-функции разработчикам необходимо обратить внимание на следующие моменты:

  1. Подобно механизму исключений Java, если вы создаете исключение (Exception) в функции обратного вызова или создаете проблемы при вызове функции JNI и позволяете JNI генерировать исключение, тогдаЛюбые исключения, возникшие до обратного вызова, теряются, что требует от разработчиков осторожности при обработке ошибок..
  2. Виртуальная машина не гарантирует, что функция обратного вызова будет синхронизирована, другими словами, программа может запускать одну и ту же callback-функцию одновременно (например, если несколько потоков запустятся одновременно, то этот HandleThreadStart будет вызываться несколько раз одновременно),Затем разработчикам необходимо решать проблемы синхронизации при разработке функций обратного вызова..

2.3.2 Управление памятью и получение объектов

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

Как мы все знаем, Java-классы, объекты и примитивные типы (Primitive) хранятся в куче Java. Работая с кучей, разработчики могут легко найти любой класс или объект.Может даже заставить работу по сбору мусора.

Работа Java-кучи в JVMTI отличается, она не обеспечивает прямого пути (Видно, что управление объектами виртуальной машиной — это не хеш-таблица, а метод дерева/графа.), но используйте метод итератора (iterator) для обхода:

jvmtiError FollowReferences(jvmtiEnv* env, 
    jint heap_filter, 
    jclass klass, 
    jobject initial_object,// 该方式可以指定根节点
    const jvmtiHeapCallbacks* callbacks,// 设置回调函数
    const void* user_data)

или

jvmtiError IterateThroughHeap(jvmtiEnv* env, 
    jint heap_filter, 
    jclass klass, 
    const jvmtiHeapCallbacks* callbacks, 
    const void* user_data)// 遍历整个 heap

В процессе обхода разработчик может задавать определенные условия, например: указать объект определенного класса, и установить функцию обратного вызова, если условие выполняется, функция обратного вызова будет выполнена. Разработчики могут пометить текущий возвращаемый указатель в функции обратного вызова - это еще одна особенность.При первом обходе могут быть помечены только объекты, которые удовлетворяют условиям, затем используйте функцию GetObjectsWithTags для получения требуемых объектов.

jvmtiError GetObjectsWithTags(jvmtiEnv* env, 
    jint tag_count, 
    const jlong* tags, // 设定特定的 tag,即我们上面所设置的
    jint* count_ptr, 
    jobject** object_result_ptr, 
    jlong** tag_result_ptr)

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

2.3.3 Потоки и блокировки

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

Разработчики могут получать блокировки, принадлежащие определенному потоку:

jvmtiError GetOwnedMonitorInfo(jvmtiEnv* env, 
    jthread thread, 
    jint* owned_monitor_count_ptr, 
    jobject** owned_monitors_ptr)

Также возможно получить блокировку, которую ожидает текущий поток:

jvmtiError GetCurrentContendedMonitor(jvmtiEnv* env, 
    jthread thread, 
    jobject* monitor_ptr)

Фактически, зная эту информацию, мы также можем разработать собственный алгоритм для определения наличия взаимоблокировки. Что еще более важно, JVMTI предоставляет ряд операций монитора (Monitor), которые помогают нам добиться синхронизации в собственной среде.

Основная операция:Монитор сборки (CreateRawMonitor), получение монитора (RawMonitorEnter), выпуск монитора (RawMonitorExit), ожидание и пробуждение монитора (RawMonitorWait, RawMonitorNotify) и другие операции., с помощью этих простых блокировок можно гарантировать синхронную работу программы.

2.3.4 Функция отладки

Функция отладки — одна из базовых функций JVMTI, которая в основном включает в себя установку точек останова, отладку (шаг) и т. д. В JVMTI сам API для установки точек останова очень прост:

jvmtiError SetBreakpoint(jvmtiEnv* env, 
    jmethodID method, 
    jlocation location)

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

2.3.5 Структура данных JVMTI

структуры данных, используемые в JVMTI,Во-первых, это также некоторые стандартные структуры данных JNI., например: jint, jlong;Во-вторых, JVMTI также определяет некоторые базовые типы., такие как: jthread, что означает поток, jvmtiEvent, что означает событие, определенное jvmti;Более сложные структуры данных JVMTI, которые должны быть представлены структурами, например: информация о куче (jvmtiStackInfo).