Отдалечено отстраняване на грешки на Java приложения

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 е протокол, използван в Java за комуникацията между debuggee и корекция на грешки . Дебъгерът е приложението, което се отстранява, докато дебъгерът е приложение или процес, свързващ се с приложението, което се отстранява.

И двете приложения се изпълняват на една и съща машина или на различни машини. Ще се спрем на последното.

3.1. Опции на JDWP

Ще използваме JDWP в аргументите на командния ред на JVM, когато стартираме приложението за отстраняване на грешки.

Извикването му изисква списък с опции:

  • транспортът е единствената напълно необходима опция. Той определя кой транспортен механизъм да се използва. dt_shmem работи само на Windows и ако двата процеса се изпълняват на една и съща машина, докато dt_socket е съвместим с всички платформи и позволява процесите да се изпълняват на различни машини
  • сървър не е задължителна опция. Този флаг, когато е включен, определя начина, по който се прикрепя към дебъгера. Той или излага процеса чрез адреса, определен в опцията за адрес . В противен случай JDWP излага такъв по подразбиране
  • suspend определя дали JVM трябва да спре и да изчака дебъгер да се прикачи или не
  • адрес е опцията, съдържаща адреса, обикновено порт, изложен от отстраняващия файл. Той може също да представлява адрес, преведен като низ от символи (като javadebug, ако използваме server = y, без да предоставяме адрес в Windows)

3.2. Команда за стартиране

Нека започнем, като стартираме отдалеченото приложение. Ще предоставим всички опции, изброени по-рано:

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

До Java 5 трябваше да се използва JVM аргументът runjdwp заедно с другата опция за отстраняване на грешки :

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

Този начин на използване на JDWP все още се поддържа, но ще отпадне в бъдещи версии. Ще предпочетем използването на по-новата нотация, когато е възможно.

3.3. От Java 9

И накрая, една от опциите на JDWP се промени с пускането на версия 9 на Java. Това е съвсем незначителна промяна, тъй като се отнася само до една опция, но ще има значение, ако се опитваме да отстраним грешки на отдалечено приложение.

Тази промяна влияе върху начина, по който адресът се държи за отдалечени приложения. По-старият адрес на нотация = 8000 се отнася само за localhost . За да постигнем старото поведение, ще използваме звездичка с двоеточие като префикс за адреса (напр. Адрес = *: 8000 ).

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

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

4. JDB: Java Debugger

JDB, Java Debugger, е инструмент, включен в JDK, създаден да осигури удобен клиент за отстраняване на грешки от командния ред.

За да стартираме JDB, ще използваме режима на прикачване . Този режим прикачва JDB към работещ JVM. Съществуват и други режими на работа, като например слушане или изпълнение, но най-вече са удобни при отстраняване на грешки в локално работещо приложение:

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

4.1. Точки на прекъсване

Нека продължим, като поставим някои точки на прекъсване в приложението, представено в раздел 1.

Ще зададем точка на прекъсване на конструктора:

> stop in OurApplication. 

Ще зададем още един в статичния метод main , като използваме напълно квалифицираното име на класа String :

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

И накрая, ще зададем последния в метода на екземпляра buildInstanceString :

> stop in OurApplication.buildInstanceString(int) 

Сега трябва да забележим, че сървърното приложение спира и следното се отпечатва в нашата конзола за отстраняване на грешки:

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

Let's now add a breakpoint on a specific line, the one where the variable app.instanceString is being printed:

> stop at OurApplication:7 

We notice that at is used after stop instead of in when the breakpoint is defined on a specific line.

4.2. Navigate and Evaluate

Now that we've set our breakpoints, let's use cont to continue the execution of our thread until we reach the breakpoint on line 7.

We should see the following printed in the console:

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

As a reminder, we've stopped on the line containing the following piece of code:

System.out.println(app.instanceString); 

Stopping on this line could have also been done by stopping on the main method and typing step twice. step executes the current line of code and stops the debugger directly on the next line.

Now that we've stopped, the debugee is evaluating our staticString, the app‘s instanceString, the local variable i and finally taking a look at how to evaluate other expressions.

Let's print staticField to the console:

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

We explicitly put the name of the class before the static field.

Let's now print the instance field of app:

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

Next, let's see the variable i:

> print i i = 68741 

Unlike the other variables, local variables don't require to specify a class or an instance. We can also see that print has exactly the same behavior as eval: they both evaluate an expression or a variable.

We'll evaluate a new instance of OurApplication for which we've passed an integer as a constructor parameter:

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

Now that we've evaluated all the variables we needed to, we'll want to delete the breakpoints set earlier and let the thread continue its processing. To achieve this, we'll use the command clear followed by the breakpoint's identifier.

The identifier is exactly the same as the one used earlier with the command 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. Заключение

I: В тази бърза статия открихме как да използваме JDWP заедно с JDB, и двата инструмента на JDK.

Повече информация за инструменталната екипировка, разбира се, може да се намери в съответните референции: JDWP и JDB - за да влезете по-дълбоко в инструментариума.