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 Debug Wire
Протокол Java Debug Wire Protocol – это протокол, используемый в Java для связи между отладчиком и отладчиком . Отладчик-это отлаживаемое приложение, в то время как отладчик-это приложение или процесс, подключающийся к отлаживаемому приложению.
Оба приложения работают либо на одной машине, либо на разных машинах. Мы сосредоточимся на последнем.
3.1. Параметры JDWP
Мы будем использовать JDWP в аргументах командной строки JVM при запуске приложения отладчика.
Для его вызова требуется список опций:
- транспорт является единственным полностью необходимым вариантом. Он определяет, какой транспортный механизм использовать. dt_shmem работает только в Windows, и если оба процесса выполняются на одной машине в то время как dt_socket совместим со всеми платформами и позволяет процессам работать на разных машинах
- сервер не является обязательным параметром. Этот флаг, когда он включен, определяет способ его подключения к отладчику. Он либо раскрывает процесс через адрес, определенный в параметре address . В противном случае JDWP предоставляет значение по умолчанию
- suspend определяет, должна ли JVM приостанавливаться и ждать подключения отладчика или нет
- address – это параметр, содержащий адрес, обычно порт, предоставляемый отладчиком. Он также может представлять адрес, переведенный в виде строки символов (например, java debug , если мы используем 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, – это инструмент, включенный в JDK, предназначенный для предоставления удобного клиента отладчика из командной строки.
Чтобы запустить JDB, мы будем использовать режим attach . Этот режим подключает JDB к запущенной JVM. Существуют и другие режимы работы, такие как listen или run , но в основном они удобны при отладке локально запущенного приложения:
jdb -attach 127.0.0.1:8000 > Initializing jdb ...
4.1. Точки останова
Давайте продолжим, поместив некоторые точки останова в приложение, представленное в разделе 1.
Мы установим точку останова в конструкторе:
> stop in OurApplication.
Мы установим еще один в статическом методе main , используя полное имя класса String :
> stop in OurApplication.main(java.lang.String[])
Наконец, мы установим последний в методе экземпляра build Instance String :
> stop in OurApplication.buildInstanceString(int)
Теперь мы должны заметить, что серверное приложение останавливается и в нашей консоли отладчика печатается следующее:
> Breakpoint hit: "thread=main", OurApplication.(), line=11 bci=0
Теперь давайте добавим точку останова в определенную строку, в которой печатается переменная app.instance String :
> stop at OurApplication:7
Мы замечаем, что at используется после stop вместо in , когда точка останова определена на определенной строке.
4.2. Навигация и оценка
Теперь, когда мы установили наши точки останова, давайте использовать cont , чтобы продолжить выполнение вашего потока, пока мы не достигнем точки останова в строке 7.
Мы должны увидеть следующее, напечатанное в консоли:
> Breakpoint hit: "thread=main", OurApplication.main(), line=7 bci=17
Напомним, что мы остановились на строке, содержащей следующий фрагмент кода:
System.out.println(app.instanceString);
Остановку в этой строке также можно было бы сделать, остановившись на методе main и дважды набрав step . шаг выполняет текущую строку кода и останавливает отладчик непосредственно на следующей строке.
Теперь, когда мы остановились , отладчик оценивает нашу статическую строку , приложение ‘s строку экземпляра , локальную переменную i и, наконец, рассматривает, как оценивать другие выражения.
Выведем статическое поле на консоль:
> eval OurApplication.staticString OurApplication.staticString = "Static String"
Мы явно ставим имя класса перед статическим полем.
Теперь давайте напечатаем поле экземпляра app :
> eval app.instanceString app.instanceString = "68741. Instance String !"
Далее, давайте посмотрим переменную i :
> print i i = 68741
В отличие от других переменных, локальные переменные не требуют указания класса или экземпляра. Мы также можем видеть, что print имеет точно такое же поведение, как evil : они оба оценивают выражение или переменную.
Мы оценим новый экземпляр нашего приложения , для которого мы передали целое число в качестве параметра конструктора:
> print new OurApplication(10).instanceString new OurApplication(10).instanceString = "10. Instance String !"
Теперь, когда мы оценили все переменные, которые нам были нужны, мы хотим удалить точки останова, установленные ранее, и позволить потоку продолжить свою обработку. Для этого мы используем команду clear , за которой следуют определенные точки останова.
Идентификатор точно такой же, как и тот, который использовался ранее с командой stop :
> clear OurApplication:7 Removed: breakpoint OurApplication:7
Чтобы проверить, правильно ли удалена точка останова, мы будем использовать clear без аргументов. Это отобразит список существующих точек останова без той, которую мы только что удалили:
> clear Breakpoints set: breakpoint OurApplication.breakpoint OurApplication.buildInstanceString(int) breakpoint OurApplication.main(java.lang.String[])
5. Заключение
Я:в этой краткой статье мы узнали, как использовать JDWP вместе с JDB, оба инструмента JDK.
Более подробную информацию об инструментах, конечно, можно найти в их соответствующих ссылках: JDWP и JDB – чтобы углубиться в инструментарий.