Remote-Debugging von Java-Anwendungen

1. Übersicht

Das Debuggen einer Remote-Java-Anwendung kann in mehreren Fällen hilfreich sein.

In diesem Tutorial erfahren Sie, wie Sie dies mit den Tools von JDK tun.

2. Die Anwendung

Beginnen wir mit dem Schreiben einer Bewerbung. Wir werden es an einem entfernten Ort ausführen und es lokal durch diesen Artikel debuggen:

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: Das Java Debug Wire Protocol

Das Java Debug Wire Protocol ist ein Protokoll, das in Java für die Kommunikation zwischen einem Debugger und einem Debugger verwendet wird . Der Debugger ist die Anwendung, die debuggt wird, während der Debugger eine Anwendung oder ein Prozess ist, der eine Verbindung zu der zu debuggenden Anwendung herstellt.

Beide Anwendungen werden entweder auf demselben Computer oder auf verschiedenen Computern ausgeführt. Wir werden uns auf Letzteres konzentrieren.

3.1. JDWP-Optionen

Wir werden JDWP in den JVM-Befehlszeilenargumenten verwenden, wenn wir die Debuggee-Anwendung starten.

Der Aufruf erfordert eine Liste von Optionen:

  • Transport ist die einzige vollständig erforderliche Option. Es definiert, welcher Transportmechanismus verwendet werden soll. dt_shmem funktioniert nur unter Windows und wenn beide Prozesse auf demselben Computer ausgeführt werden, während dt_socket mit allen Plattformen kompatibel ist und die Prozesse auf verschiedenen Computern ausgeführt werden können
  • Server ist keine obligatorische Option. Wenn dieses Flag aktiviert ist, wird die Art und Weise definiert, wie es an den Debugger angehängt wird. Entweder wird der Prozess über die in der Adressoption definierte Adresse verfügbar gemacht. Andernfalls macht JDWP einen Standard verfügbar
  • suspend legt fest, ob die JVM angehalten werden soll und warten soll, bis ein Debugger angehängt wird oder nicht
  • Adresse ist die Option, die die Adresse enthält, im Allgemeinen einen Port, der vom Debugger verfügbar gemacht wird. Es kann auch eine Adresse darstellen, die als Zeichenfolge übersetzt wurde (wie Javadebug, wenn wir server = y verwenden, ohne unter Windows eine Adresse anzugeben ).

3.2. Befehl starten

Beginnen wir mit dem Starten der Remote-Anwendung. Wir werden alle zuvor aufgeführten Optionen bereitstellen:

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

Bis Java 5 musste das JVM-Argument runjdwp zusammen mit der anderen Option debug verwendet werden :

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

Diese Art der Verwendung von JDWP wird weiterhin unterstützt, wird jedoch in zukünftigen Versionen nicht mehr verwendet. Wenn möglich, bevorzugen wir die Verwendung der neueren Notation.

3.3. Seit Java 9

Schließlich hat sich eine der Optionen von JDWP mit der Veröffentlichung von Version 9 von Java geändert. Dies ist eine geringfügige Änderung, da es sich nur um eine Option handelt, die jedoch einen Unterschied macht, wenn wir versuchen, eine Remoteanwendung zu debuggen.

Diese Änderung wirkt sich auf das Verhalten der Adresse für Remoteanwendungen aus. Die ältere Notationsadresse = 8000 gilt nur für localhost . Um das alte Verhalten zu erreichen, verwenden wir ein Sternchen mit einem Doppelpunkt als Präfix für die Adresse (z. B. Adresse = *: 8000 ).

Laut Dokumentation ist dies nicht sicher und es wird empfohlen, die IP-Adresse des Debuggers nach Möglichkeit anzugeben:

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

4. JDB: Der Java-Debugger

JDB, der Java-Debugger, ist ein im JDK enthaltenes Tool, das einen praktischen Debugger-Client über die Befehlszeile bereitstellen soll.

So starten Sie JDB, werden wir die Verwendung anhängen Modus. In diesem Modus wird JDB an eine laufende JVM angehängt. Es gibt andere Ausführungsmodi wie Listen oder Run, die jedoch beim Debuggen einer lokal ausgeführten Anwendung meistens praktisch sind:

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

4.1. Haltepunkte

Lassen Sie uns fortfahren, indem wir einige Haltepunkte in die in Abschnitt 1 vorgestellte Anwendung einfügen.

Wir setzen einen Haltepunkt für den Konstruktor:

> stop in OurApplication. 

Wir werden eine weitere in der statischen Methode main festlegen , wobei wir den vollständig qualifizierten Namen der String- Klasse verwenden:

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

Schließlich setzen wir die letzte für die Instanzmethode buildInstanceString :

> stop in OurApplication.buildInstanceString(int) 

Wir sollten jetzt bemerken, dass die Serveranwendung stoppt und Folgendes in unserer Debugger-Konsole gedruckt wird:

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

Fügen wir nun einen Haltepunkt in eine bestimmte Zeile ein, in der die Variable app.instanceString gedruckt wird:

> stop at OurApplication:7 

Wir stellen fest, dass at nach stop anstelle von in verwendet wird, wenn der Haltepunkt in einer bestimmten Zeile definiert ist.

4.2. Navigieren und auswerten

Nachdem wir unsere Haltepunkte festgelegt haben, setzen wir cont fort , um die Ausführung unseres Threads fortzusetzen, bis wir den Haltepunkt in Zeile 7 erreichen.

In der Konsole sollte Folgendes gedruckt sein:

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

Zur Erinnerung, wir haben in der Zeile mit dem folgenden Code angehalten:

System.out.println(app.instanceString); 

Stoppen auf dieser Linie auch durch Anhalten auf dem geschehen könnte Hauptverfahren und die Eingabe Schritt zweimal. step führt die aktuelle Codezeile aus und stoppt den Debugger direkt in der nächsten Zeile.

Jetzt, wo wir aufgehört haben, wird die debugee unserer Auswertung staticString , die App ‚s instanceString , die lokale Variable i und schließlich einen Blick auf , wie andere Ausdrücke zu bewerten.

Drucken wir staticField auf der Konsole aus:

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

Wir setzen den Namen der Klasse explizit vor das statische Feld.

Drucken wir nun das Instanzfeld der App :

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

Als nächstes sehen wir uns die Variable i an :

> 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 

Um zu überprüfen, ob der Haltepunkt korrekt entfernt wurde, verwenden wir clear ohne Argumente. Dadurch wird die Liste der vorhandenen Haltepunkte ohne den gerade gelöschten angezeigt:

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

5. Schlussfolgerung

In diesem kurzen Artikel haben wir herausgefunden, wie JDWP zusammen mit JDB, beiden JDK-Tools, verwendet wird.

Weitere Informationen zu den Werkzeugen finden Sie natürlich in den jeweiligen Referenzen: JDWPs und JDBs - um tiefer in die Werkzeuge einzusteigen.