Перейти к основному содержимому

Удаленная отладка приложений Java

· 6 мин. чтения

1. Обзор

Отладка удаленного Java-приложения может быть полезна в нескольких случаях.

В этом руководстве мы узнаем, как это сделать с помощью инструментов JDK.

2. Приложение

Начнем с написания заявки. Мы запустим его в удаленном месте и отладим локально в этой статье:

public class OurApplication {
private static String staticString = "Static String";
private String instanceString;

public static void main(String[] args) {
for (int i = 0; i < 1_000_000_000; i++) {
OurApplication app = new OurApplication(i);
System.out.println(app.instanceString);
}
}

public OurApplication(int index) {
this.instanceString = buildInstanceString(index);
}

public String buildInstanceString(int number) {
return number + ". Instance String !";
}
}

3. JDWP: проводной протокол отладки Java

Java Debug Wire Protocol — это протокол, используемый в Java для связи между отлаживаемой программой и отладчиком . Отлаживаемый объект — это отлаживаемое приложение, а отладчик — это приложение или процесс, подключающийся к отлаживаемому приложению.

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

3.1. Опции JDWP

Мы будем использовать JDWP в аргументах командной строки JVM при запуске отлаживаемого приложения.

Для его вызова требуется список опций:

  • транспорт - единственный полноценный вариант. Он определяет, какой транспортный механизм использовать. dt_shmem работает только в Windows и если оба процесса работают на одном компьютере , а dt_socket совместим со всеми платформами и позволяет запускать процессы на разных компьютерах.
  • server не является обязательным параметром. Если этот флаг установлен, он определяет способ подключения к отладчику. Он либо предоставляет процесс через адрес, указанный в опции адреса . В противном случае JDWP выставляет значение по умолчанию.
  • suspend определяет, должна ли JVM приостанавливаться и ждать подключения отладчика или нет.
  • address — это опция, содержащая адрес, как правило, порт, предоставляемый отлаживаемой программой. Он также может представлять адрес, переведенный в виде строки символов (например , javadebug , если мы используем server=y без указания адреса в Windows) .

3.2. Команда запуска

Начнем с запуска удаленного приложения. Мы предоставим все варианты, перечисленные ранее:

java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8000 OurApplication

До Java 5 аргумент JVM runjdwp нужно было использовать вместе с другим параметром debug :

java -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=8000

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

3.3. Начиная с Java 9

Наконец, один из вариантов JDWP изменился с выходом версии 9 Java. Это довольно незначительное изменение, так как оно касается только одного параметра, но будет иметь значение, если мы пытаемся отладить удаленное приложение.

Это изменение влияет на поведение адреса для удаленных приложений. Старая нотация address=8000 применяется только к localhost . Чтобы добиться старого поведения, мы будем использовать звездочку с двоеточием в качестве префикса для адреса (например , address=*:8000 ).

Согласно документации, это небезопасно, и рекомендуется по возможности указывать IP-адрес отладчика:

java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=127.0.0.1:8000

4. JDB: отладчик Java

JDB, Java Debugger, — это инструмент, включенный в JDK, предназначенный для предоставления удобного клиента отладчика из командной строки.

Для запуска JDB мы будем использовать режим подключения . В этом режиме JDB подключается к работающей JVM. Существуют и другие режимы работы, такие как прослушивание или выполнение , но в основном они удобны при отладке локально запущенного приложения:

jdb -attach 127.0.0.1:8000
> Initializing jdb ...

4.1. Контрольные точки

Давайте продолжим, поставив несколько точек останова в приложении, представленном в разделе 1.

Мы установим точку останова в конструкторе:

> stop in OurApplication.<init>

Мы установим еще один в статическом методе main , используя полное имя класса String :

> stop in OurApplication.main(java.lang.String[])

Наконец, мы установим последний в методе экземпляра buildInstanceString :

> stop in OurApplication.buildInstanceString(int)

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

> Breakpoint hit: "thread=main", OurApplication.<init>(), line=11 bci=0

Давайте теперь добавим точку останова на определенную строку, где печатается переменная app.instanceString :

> stop at OurApplication:7

Мы заметили, что at используется после остановки , а не in , когда точка останова определена на определенной строке.

4.2. Навигация и оценка

Теперь, когда мы установили наши точки останова, давайте используем cont для продолжения выполнения нашего потока, пока мы не достигнем точки останова в строке 7.

В консоли мы должны увидеть следующее:

> Breakpoint hit: "thread=main", OurApplication.main(), line=7 bci=17

Напоминаем, что мы остановились на строке, содержащей следующий фрагмент кода:

System.out.println(app.instanceString);

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

Теперь, когда мы остановились, отлаживаемый объект оценивает нашу staticString , instanceString приложения , локальную переменную i и, наконец, изучает, как оценивать другие выражения.

Выведем staticField в консоль:

> eval OurApplication.staticString
OurApplication.staticString = "Static String"

Мы явно помещаем имя класса перед статическим полем.

Давайте теперь напечатаем поле экземпляра приложения :

> eval app.instanceString
app.instanceString = "68741. Instance String !"

Далее давайте посмотрим на переменную i :

> print i
i = 68741

В отличие от других переменных, для локальных переменных не требуется указывать класс или экземпляр. Мы также можем видеть, что print ведет себя точно так же, как и eval : обе они оценивают выражение или переменную.

Мы оценим новый экземпляр OurApplication, для которого мы передали целое число в качестве параметра конструктора:

> print new OurApplication(10).instanceString
new OurApplication(10).instanceString = "10. Instance String !"

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

Идентификатор точно такой же, как тот, который использовался ранее с командой stop :

> clear OurApplication:7
Removed: breakpoint OurApplication:7

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

> clear
Breakpoints set:
breakpoint OurApplication.<init>
breakpoint OurApplication.buildInstanceString(int)
breakpoint OurApplication.main(java.lang.String[])

5. Вывод

В этой быстрой статье мы узнали, как использовать JDWP вместе с JDB, оба инструмента JDK.

Дополнительную информацию об инструментах можно, конечно, найти в соответствующих справочниках: JDWP и JDB — чтобы углубиться в инструменты.