Foutopsporing op afstand in Java-toepassingen

1. Overzicht

Het debuggen van een externe Java-applicatie kan in meer dan één geval handig zijn.

In deze tutorial zullen we ontdekken hoe je dat doet met behulp van de tooling van JDK.

2. De applicatie

Laten we beginnen met het schrijven van een aanvraag. We zullen het op een externe locatie uitvoeren en lokaal debuggen via dit artikel:

openbare klasse OurApplication {privé statische String staticString = "Statische string"; private String instanceString; public static void main (String [] args) {for (int i = 0; i <1_000_000_000; i ++) {OurApplication app = nieuwe OurApplication (i); System.out.println (app.instanceString); }} openbare OurApplication (int index) {this.instanceString = buildInstanceString (index); } public String buildInstanceString (int number) {return number + ". Instance String!"; }} 

3. JDWP: het Java Debug Wire Protocol

De Java Debug Wire-protocolis een protocol dat in Java wordt gebruikt voor de communicatie tussen een debuggee en een debugger. De debuggee is de toepassing die wordt opgespoord, terwijl de debugger een toepassing of een proces is dat verbinding maakt met de toepassing die wordt opgespoord.

Beide applicaties draaien op dezelfde machine of op verschillende machines. We zullen ons concentreren op het laatste.

3.1. JDWP's opties

We gebruiken JDWP in de JVM-opdrachtregelargumenten bij het starten van de foutopsporingsapplicatie.

Het aanroepen ervan vereist een lijst met opties:

  • vervoer- is de enige volledig vereiste optie. Het bepaalt welk transportmechanisme moet worden gebruikt. dt_shmem werkt alleen op Windows en als beide processen op dezelfde machine draaien terwijl dt_socket is compatibel met alle platforms en laat de processen op verschillende machines draaien
  • server is geen verplichte optie. Deze vlag, indien ingeschakeld, definieert de manier waarop deze wordt gekoppeld aan de debugger. Het stelt ofwel het proces bloot via het adres dat is gedefinieerd in het adres keuze. Anders geeft JDWP een standaardbericht weer
  • opschorten bepaalt of de JVM moet worden onderbroken en moet wachten tot een foutopsporingsprogramma is gekoppeld of niet
  • adres is de optie die het adres bevat, meestal een poort, die door de debuggee wordt getoond. Het kan ook een adres vertegenwoordigen dat is vertaald als een reeks tekens (zoals javadebug als we gebruiken server = y zonder een adres op Windows)

3.2. Start het commando

Laten we beginnen met het starten van de externe applicatie. We bieden alle eerder genoemde opties:

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

Tot Java 5, het JVM-argument runjdwp moest samen met de andere optie worden gebruikt debuggen:

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

Deze manier om JDWP te gebruiken wordt nog steeds ondersteund, maar zal in toekomstige releases worden geschrapt. We geven de voorkeur aan het gebruik van de nieuwere notatie indien mogelijk.

3.3. Sinds Java 9

Ten slotte is een van de opties van JDWP veranderd met de release van versie 9 van Java. Dit is een vrij kleine wijziging, aangezien het slechts om één optie gaat, maar het zal een verschil maken als we proberen een toepassing op afstand te debuggen.

Deze verandering heeft invloed op de weg adres gedraagt ​​zich voor externe toepassingen. De oudere notatie adres = 8000 is alleen van toepassing op localhost. Om het oude gedrag te bereiken, gebruiken we een asterisk met een dubbele punt als voorvoegsel voor het adres (bijv adres = *: 8000).

Volgens de documentatie is dit niet veilig en wordt aanbevolen om het IP-adres van de debugger waar mogelijk op te geven:

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

4. JDB: de Java Debugger

JDB, de Java Debugger, is een tool in de JDK die is ontworpen om een ​​handige debugger-client te bieden vanaf de opdrachtregel.

Om JDB te starten, gebruiken we de vastmaken modus. Deze modus koppelt JDB aan een actieve JVM. Er zijn nog andere bedrijfsmodi, zoals luister of rennen maar zijn meestal handig bij het debuggen van een lokaal draaiende applicatie:

jdb -attach 127.0.0.1:8000> Initialiseren van jdb ... 

4.1. Breekpunten

Laten we doorgaan met het plaatsen van enkele breekpunten in de toepassing die wordt gepresenteerd in sectie 1.

We stellen een breekpunt in op de constructor:

> stop in OurApplication. 

We zullen er nog een instellen in de statische methode hoofd, met behulp van de volledig gekwalificeerde naam van het Draad klasse:

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

Ten slotte stellen we de laatste in op de instantiemethode buildInstanceString:

> stop in OurApplication.buildInstanceString (int) 

We zouden nu moeten opmerken dat de servertoepassing stopt en het volgende wordt afgedrukt in onze debugger-console:

> Breekpunttreffer: "thread = main", OurApplication. (), Line = 11 bci = 0 

Laten we nu een breekpunt toevoegen op een specifieke regel, degene waar de variabele app.instanceString wordt geprint:

> stop bij OurApplication: 7 

Dat merken we Bij wordt gebruikt na hou op in plaats van in wanneer het breekpunt is gedefinieerd op een specifieke lijn.

4.2. Navigeer en evalueer

Nu we onze breekpunten hebben ingesteld, gaan we gebruiken vervolg om de uitvoering van onze thread voort te zetten totdat we het breekpunt op regel 7 bereiken.

We zouden het volgende moeten zien afgedrukt in de console:

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

Ter herinnering, we zijn gestopt op de regel met het volgende stuk code:

System.out.println (app.instanceString); 

Stoppen op deze lijn had ook kunnen gebeuren door te stoppen op de hoofd methode en typen stap tweemaal. stap voert de huidige regel code uit en stopt de debugger direct op de volgende regel.

Nu we zijn gestopt, evalueert de debuge onze staticString, de app‘S instanceString, de lokale variabele ik en tot slot kijken hoe je andere uitdrukkingen kunt evalueren.

Laten we afdrukken staticField naar de console:

> eval OurApplication.staticString OurApplication.staticString = "Statische tekenreeks" 

We plaatsen expliciet de naam van de klasse voor het statische veld.

Laten we nu het instantieveld van app:

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

Laten we vervolgens de variabele bekijken ik:

> print ik ik = 68741 

In tegenstelling tot de andere variabelen, hoeven lokale variabelen geen klasse of instantie op te geven. Dat kunnen we ook zien afdrukken heeft precies hetzelfde gedrag als eval: ze evalueren allebei een uitdrukking of een variabele.

We evalueren een nieuw exemplaar van OurApplication waarvoor we een geheel getal hebben doorgegeven als een constructorparameter:

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

Nu we alle variabelen hebben geëvalueerd die we nodig hadden, willen we de eerder ingestelde breekpunten verwijderen en de thread verder laten verwerken. Om dit te bereiken, gebruiken we de opdracht Doorzichtig gevolgd door de identificatie van het breekpunt.

De identifier is exact hetzelfde als degene die eerder bij de opdracht werd gebruikt hou op:

> clear OurApplication: 7 Verwijderd: breekpunt OurApplication: 7 

Om te controleren of het breekpunt correct is verwijderd, gebruiken we Doorzichtig zonder argumenten. Hierdoor wordt de lijst met bestaande breekpunten weergegeven zonder degene die we zojuist hebben verwijderd:

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

5. Conclusie

In dit korte artikel hebben we ontdekt hoe we JDWP samen met JDB, beide JDK-tools, kunnen gebruiken.

Meer informatie over de tooling is natuurlijk te vinden in hun respectievelijke referenties: JDWP's en JDB's - om dieper in te gaan op de tooling.