Översikt

Debuggning av ett fjärrstyrt Java-program kan vara praktiskt i mer än ett fall.

I den här handledningen kommer vi att upptäcka hur man gör det med hjälp av JDK:s verktyg.

Applicationen

Låtsas oss börja med att skriva ett program. Vi kör den på en fjärrplats och felsöker den lokalt med hjälp av den här artikeln:

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 !"; }}

JDWP: The Java Debug Wire Protocol

The Java Debug Wire Protocol är ett protokoll som används i Java för kommunikationen mellan en felsökare och en felsökare. Debuggee är det program som debuggas medan debuggaren är ett program eller en process som ansluter till det program som debuggas.

Båda programmen körs antingen på samma maskin eller på olika maskiner. Vi kommer att fokusera på det senare.

3.1. JDWP:s alternativ

Vi kommer att använda JDWP i JVM:s kommandoradsargument när vi startar debuggeprogrammet.

Dess åberopande kräver en lista med alternativ:

  • transport är det enda helt nödvändiga alternativet. Det definierar vilken transportmekanism som ska användas. dt_shmem fungerar endast på Windows och om båda processerna körs på samma maskin medan dt_socket är kompatibelt med alla plattformar och gör det möjligt för processerna att köras på olika maskiner
  • server är inte ett obligatoriskt alternativ. Denna flagga, när den är på, definierar hur den ansluter sig till felsökaren. Den exponerar antingen processen genom den adress som definieras i adressalternativet. Annars exponerar JDWP en standard
  • suspend definierar om JVM:n ska suspendera och vänta på att en felsökare ansluter sig eller inte
  • address är alternativet som innehåller den adress, i allmänhet en port, som exponeras av felsökaren. Det kan också representera en adress översatt som en teckensträng (som javadebug om vi använder server=y utan att ange en adress på Windows)

3.2. Startkommando

Låt oss börja med att starta fjärrprogrammet. Vi kommer att tillhandahålla alla de alternativ som tidigare har listats:

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

Till och med Java 5 måste JVM-argumentet runjdwp användas tillsammans med det andra alternativet debug:

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

Detta sätt att använda JDWP stöds fortfarande, men kommer att slopas i framtida versioner. Vi kommer att föredra användningen av den nyare notationen när det är möjligt.

3.3. Sedan Java 9

Enligt har ett av alternativen i JDWP ändrats i och med lanseringen av version 9 av Java. Detta är en ganska liten ändring eftersom den bara gäller ett alternativ men kommer att göra skillnad om vi försöker felsöka ett fjärrprogram.

Denna ändring påverkar hur adressen beter sig för fjärrprogram. Den äldre notationen address=8000 gäller endast för localhost. För att uppnå det gamla beteendet använder vi en asterisk med ett kolon som prefix för adressen (t.ex. address=*:8000).

Enligt dokumentationen är detta inte säkert och det rekommenderas att ange debuggarens IP-adress när det är möjligt:

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

JDB: The Java Debugger

JDB, Java Debugger, är ett verktyg som ingår i JDK som är tänkt att ge en bekväm debuggerklient från kommandoraden.

För att starta JDB använder vi attach mode. Detta läge kopplar JDB till en pågående JVM. Det finns andra körningslägen, till exempel listen eller run, men de är mest praktiska vid felsökning av ett lokalt kört program:

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

4.1. Brytpunkter

Låt oss fortsätta med att sätta några brytpunkter i programmet som presenterades i avsnitt 1.

Vi sätter en brytpunkt på konstruktören:

> stop in OurApplication.<init>

Vi sätter ytterligare en i den statiska metoden main, som använder det fullt kvalificerade namnet på String-klassen:

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

Slutligt sätter vi den sista på instansmetoden buildInstanceString:

> stop in OurApplication.buildInstanceString(int)

Vi bör nu märka att serverapplikationen stannar och att följande skrivs ut i vår felsökarkonsol:

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

Vi lägger nu till en brytpunkt på en specifik rad, den där variabeln app.instanceString skrivs ut:

> stop at OurApplication:7

Vi märker att at används efter stop istället för in när brytpunkten definieras på en specifik rad.

4.2. Navigera och utvärdera

Nu när vi har ställt in våra brytpunkter, låt oss använda cont för att fortsätta exekveringen av vår tråd tills vi når brytpunkten på rad 7.

Vi bör se följande skrivas ut i konsolen:

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

Som en påminnelse har vi stannat på den rad som innehåller följande kodstycke:

System.out.println(app.instanceString);

Stoppet på den här linjen hade också kunnat göras genom att stanna på main-metoden och skriva step två gånger. step exekverar den aktuella kodraden och stoppar felsökaren direkt på nästa rad.

Nu när vi har stannat utvärderar debuggaren vår staticString, appens instanceString, den lokala variabeln i och tar slutligen en titt på hur man utvärderar andra uttryck.

Låt oss skriva ut staticField till konsolen:

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

Vi har uttryckligen satt klassens namn före det statiska fältet.

Låt oss nu skriva ut instansfältet för app:

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

Nästan, låt oss se variabeln i:

> print ii = 68741

Till skillnad från de andra variablerna behöver lokala variabler inte ange en klass eller en instans. Vi kan också se att print har exakt samma beteende som eval: båda utvärderar ett uttryck eller en variabel.

Vi utvärderar en ny instans av OurApplication för vilken vi har överlämnat ett heltal som konstruktörsparameter:

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

Nu när vi har utvärderat alla variabler vi behövde, vill vi ta bort de brytpunkter som sattes tidigare och låta tråden fortsätta sin bearbetning. För att uppnå detta använder vi kommandot clear följt av brytpunktens identifierare.

Identifieraren är exakt densamma som den som användes tidigare med kommandot stop:

> clear OurApplication:7Removed: breakpoint OurApplication:7

För att verifiera om brytpunkten har tagits bort på rätt sätt använder vi clear utan argument. Detta kommer att visa listan över befintliga brytpunkter utan den vi just tagit bort:

> clearBreakpoints set: breakpoint OurApplication.<init> breakpoint OurApplication.buildInstanceString(int) breakpoint OurApplication.main(java.lang.String)

Slutsats

I:n denna snabba artikel har vi upptäckt hur man använder JDWP tillsammans med JDB, båda JDK-verktyg.