Санкт-Петербургская группа тестирования JVM


« Вышел Java Tutorial... | Main | Новые оптимизации... »
20060816 среда Август 16, 2006

Используем JDI

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

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

JPDA состоит из трёх основных интерфейсов:

Реализация JDI доступна вместе с JDK начиная с версии 1.3 и, конечно же, от версии к версии предоставляет всё больше возможностей. Классы, реализующие JDI, находятся в jar-файле <JDK>/lib/tools.jar. Также JDK включает несколько интересных примеров программ использующих JDI: <JDK>/demo/jpda/examples.jar. При компиляции и запуске примеров путь к классам должен включать tools.jar.

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

Прежде чем начать работать с отлаживаемой программой отладчик должен установить с ней соединение. Есть несколько способовсделать это. JDI определяет абстракцию, называемую коннектор (Connector), которая инкапсулирует процесс установки соединения между отладчиком (написанным с использованием JDI) и отлаживаемой виртуальной машиной. Различные реализации JDI могут поддерживать работу со специфичными виртуальными машинами, например, с виртуальными машинами, для встраиваемых (embedded) устройств. В таких случаях может понадобиться реализовать специальный коннектор.

Коннекторы делятся на 3 типа:

Другая абстракция, используемая в JPDA—это транспорт (Transport). Транспорт это способ передачи данных между отладчиком и отлаживаемой ВМ. В ВМ компании Sun доступны 2 вида транспортов:

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

Рассмотрим пример использования коннектора, доступного в ВМ компании Sun (в приведённых примерах используются сокращённые имена классов без указания имён пакетов, все используемыеклассы расположены в следующих пакетах: com.sun.jdi, com.sun.jdi.connect, com.sun.jdi.event, com.sun.jdi.request).

Сначала необходимо получить объект VirtualMachineManager, это делается с помощью класса Bootstrap, который содержит всего один метод:

	VirtualMachineManager virtualMachineManager = Bootstrap.virtualMachineManager()

Списки доступных коннекторов можно получить, используя следующие методы VirtualMachineManager: attachingConnectors(), listeningConnectors(), launchingConnectors().

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

Один из доступных запускающих коннекторов—com.sun.jdi.CommandLineLaunch, этот коннектор требует только один обязательный параметр—имя главного класса отлаживаемого приложения:

        // получаем список всех запускающих коннекторов
for (LaunchingConnector launchingConnector : virtualMachineManager.launchingConnectors()) {
// ищем коннектор с заданным именем
if (launchingConnector.name().equals("com.sun.jdi.CommandLineLaunch")) {
// получаем список аргументов коннектора по умолчанию
Map arguments = launchingConnector.defaultArguments();

// получаем аргумент 'main' - имя главного класса отлаживаемого приложения
Argument argument = (Connector.StringArgument)arguments.get("main");
argument.setValue("ApplicationMainClass");

try {
// запускаем отлаживаемое приложение и получаем объект VirtualMachine
VirtualMachine vm = launchingConnector.launch(arguments);
} catch (Exception e) {
e.printStackTrace();
}
}
}

Также доступны более специфичные коннекторы: например, есть коннекторы, позволяющие работать с core-файлами (sun.jvm.hotspot.jdi.SACoreAttachingConnector) или с процессами, которые были запущены без каких-либо специальных опций, таких как -agentlib:jdwp (sun.jvm.hotspot.jdi.SAPIDAttachingConnector).

После того как получен объект VirtualMachine, у отладчика появляется доступ к различной информации об отлаживаемой ВМ и есть возможность влиять на её состояние. JDI может использоваться для создания всевозможных программ дляконтроля состояния ВМ, например, новые возможности появившеся в JDK6 позволяют получать информацию о всех объектах находящихся в heap-памяти отлаживаемой ВМ (heap walking), один из вариантов использования этой возможности—создание средств для обнаружения утечек памяти.

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

    ClassPrepareRequest request = virtualMachine.eventRequestManager().createClassPrepareRequest();
request.enable();

С помощью метода EventRequest.setSuspendPolicy(int policy) для запроса можно задать режим временной остановки (suspend policy), где policy может принимать одно из следующих значений:

После того как запрос создан можно запросить полученные события из очереди события:

    EventSet eventSet = virtualMachine.eventQueue().remove()

Так как метод EventQueue().remove() блокирует вызывающий поток пока не получено событие, для получения событий как правило запускается отдельный поток, который постоянно опрашивает очередь.

Рассмотрим еще пример приложения, использующего JDI. Следующая программа отслеживает в отлаживаемой программе загружаемые классы с помощью обработки событий типа ClassPrepareEvent:

// JDIExample.java

import java.util.*;
import com.sun.jdi.*;
import com.sun.jdi.connect.*;
import com.sun.jdi.connect.Connector.Argument;
import com.sun.jdi.event.*;
import com.sun.jdi.request.*;

// отлаживаемое приложение
class TargetApplication {

public static void main(String args[]) {
System.out.println("Привет, мир!");
}
}

// интерфейс слушателя событий
interface EventListener {

// получено событие VMStartEvent
public void vmStartEvent(VMStartEvent event);

// получено событие ClassPrepareEvent
public void classPrepareEvent(ClassPrepareEvent event);
}

public class JDIExample implements EventListener {

// поток опрашивает очередь событий и передаёт полученные события слушателю
// (EventListener) до тех пор, пока не получено событие VMDisconnectEvent
public class EventListenerThread extends Thread {

private EventListener eventListener;

public EventListenerThread(EventListener eventListener) {
this.eventListener = eventListener;
}

public void run() {
try {
while (true) {
EventQueue eventQueue = virtualMachine.eventQueue();

EventSet eventSet = eventQueue.remove();

EventIterator eventIterator = eventSet.eventIterator();

while (eventIterator.hasNext()) {
Event event = eventIterator.nextEvent();

if (event instanceof VMDisconnectEvent) {
// получаем это событие когда потеряно соединение с ВМ
// (ВМ завершила работу)
System.out.println("потеряно соединение с ВМ");
return;
} else if (event instanceof VMStartEvent) {
eventListener.vmStartEventCallback((VMStartEvent) event);
} else if (event instanceof ClassPrepareEvent) {
eventListener.classPrepareEventCallback((ClassPrepareEvent) event);
}
}
}
} catch (Exception e) {
// для простоты игнорируем все исключительные ситуации
e.printStackTrace();
}
}
}

public void vmStartEventCallback(VMStartEvent event) {
System.out.println("ВМ запущена");

// создаём запрос для получения событий ClassPrepareEvent
ClassPrepareRequest request =
virtualMachine.eventRequestManager().createClassPrepareRequest();

// не приостанавливать ВМ при наступлении событий
request.setSuspendPolicy(EventRequest.SUSPEND_NONE);
request.enable();

// по умолчанию политика приостановки для события VMStartEvent--
// SUSPEND_ALL, поэтому после получения этого события надо вызвать
// VirtualMachine.resume()
virtualMachine.resume();
}

public void classPrepareEventCallback(ClassPrepareEvent event) {
System.out.println("Класс '" + event.referenceType().name() + "' был подготовлен");
}

VirtualMachine virtualMachine;

// запустить ВМ
VirtualMachine startTargetVM(String mainClassName) {

VirtualMachineManager virtualMachineManager = Bootstrap.virtualMachineManager();

LaunchingConnector connector = virtualMachineManager.defaultConnector();

Map arguments = connector.defaultArguments();
Argument arg = (Connector.StringArgument) arguments.get("main");
// указываем главный класс отлаживаемого приложения
arg.setValue(mainClassName);

try {
VirtualMachine vm = connector.launch(arguments);
return vm;
} catch (Exception e) {
System.out.println("Ошибка при запуске ВМ: " + e);
e.printStackTrace();
}

return null;
}

public void execute() {
virtualMachine = startTargetVM("TargetApplication");

if (virtualMachine == null)
return;

EventListenerThread listenerThread = new EventListenerThread(this);
listenerThread.start();

try {
listenerThread.join();
} catch (InterruptedException e) {
}
}

public static void main(String args[]) {
new JDIExample().execute();
}
}

При компиляции и запуске не забываем указать расположение tools.jar и затем смотрим, сколько классов надо загрузить для выполнения приложения "Привет, мир!":

% /usr/java/jdk1.6.0/bin/javac -classpath /usr/java/jdk1.6.0/lib/tools.jar JDIExample.java
% /usr/java/jdk1.6.0/bin/java -classpath .:/usr/java/jdk1.6.0/lib/tools.jar JDIExample
ВМ запущена
Класс 'java.lang.ClassNotFoundException' был подготовлен
Класс 'java.net.URLClassLoader$1' был подготовлен
Класс 'sun.misc.URLClassPath$3' был подготовлен
Класс 'sun.misc.URLClassPath$Loader' был подготовлен
Класс 'sun.misc.URLClassPath$JarLoader' был подготовлен
Класс 'java.lang.StringBuffer' был подготовлен
Класс 'java.lang.Short' был подготовлен
Класс 'java.security.PrivilegedActionException' был подготовлен
Класс 'sun.misc.URLClassPath$FileLoader' был подготовлен
Класс 'sun.misc.Resource' был подготовлен
Класс 'sun.misc.URLClassPath$FileLoader$1' был подготовлен
Класс 'java.security.CodeSource' был подготовлен
Класс 'java.security.PermissionCollection' был подготовлен
Класс 'java.security.Permissions' был подготовлен
Класс 'java.net.URLConnection' был подготовлен
Класс 'sun.net.www.URLConnection' был подготовлен
Класс 'sun.net.www.protocol.file.FileURLConnection' был подготовлен
Класс 'java.net.ContentHandler' был подготовлен
Класс 'java.net.UnknownContentHandler' был подготовлен
Класс 'sun.net.www.MessageHeader' был подготовлен
Класс 'java.io.FilePermission' был подготовлен
Класс 'java.io.FilePermission$1' был подготовлен
Класс 'java.security.Policy' был подготовлен
Класс 'sun.security.provider.PolicyFile' был подготовлен
Класс 'java.security.Policy$UnsupportedEmptyCollection' был подготовлен
Класс 'java.io.FilePermissionCollection' был подготовлен
Класс 'java.security.BasicPermissionCollection' был подготовлен
Класс 'java.security.ProtectionDomain' был подготовлен
Класс 'TargetApplication' был подготовлен
Класс 'sun.nio.cs.Surrogate' был подготовлен
Класс 'java.util.AbstractList$Itr' был подготовлен
Класс 'java.util.IdentityHashMap$KeySet' был подготовлен
Класс 'java.util.IdentityHashMap$IdentityHashMapIterator' был подготовлен
Класс 'java.util.IdentityHashMap$KeyIterator' был подготовлен
Класс 'java.io.DeleteOnExitHook' был подготовлен
Класс 'java.util.LinkedHashSet' был подготовлен
Класс 'java.util.HashMap$KeySet' был подготовлен
Класс 'java.util.LinkedHashMap$LinkedHashIterator' был подготовлен
Класс 'java.util.LinkedHashMap$KeyIterator' был подготовлен
потеряно соединение с ВМ

Приведённый пример очень простой из-за ограничений на размер, накладываемых рамками поста, на самом делевозможности приложения, использующего JDI, ограничены только вашей фантазией. Удачи!

Семён Бойков

опубликовал vmrobot ( авг 16 2006, 07:24:13 PM MSD ) Permalink Комментарии [0]

Trackback URL: http://blogs.sun.com/vmrobot/entry/%D0%B8%D1%81%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D1%83%D0%B5%D0%BC_jdi
Комментарии:

Опубликовать комментарий:

Имя
E-Mail:
URL:

Ваш комментарий:

HTML Syntax: Отключен

Хиты страниц за сегодня: 171