Yleiskatsaus

Java-sovelluksen etäkorjausvirheenkorjaus voi olla kätevää useammassa kuin yhdessä tapauksessa.

Tässä opetusohjelmassa selvitämme, miten se onnistuu JDK:n työkalujen avulla.

Sovellus

Aloitetaan kirjoittamalla sovellus. Ajamme sen etäkäytössä ja debuggaamme sen paikallisesti tämän artikkelin avulla:

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

Java Debug Wire Protocol on Java-ohjelmassa käytetty protokolla, jota käytetään debuggerin ja debuggerin väliseen kommunikointiin. Debuggee on debugattava sovellus, kun taas debuggeri on sovellus tai prosessi, joka on yhteydessä debugattavaan sovellukseen.

Kummatkin sovellukset toimivat joko samalla koneella tai eri koneilla. Keskitymme jälkimmäiseen.

3.1. JDWP:n asetukset

Käytämme JDWP:tä JVM:n komentorivin argumenteissa, kun käynnistämme debuggee-sovelluksen.

JDWP:n kutsuminen vaatii listan asetuksia:

  • transport on ainoa täysin pakollinen vaihtoehto. Se määrittelee, mitä kuljetusmekanismia käytetään. dt_shmem toimii vain Windowsissa ja jos molemmat prosessit suoritetaan samalla koneella, kun taas dt_socket on yhteensopiva kaikkien alustojen kanssa ja mahdollistaa prosessien suorittamisen eri koneilla
  • server ei ole pakollinen vaihtoehto. Tämä lippu, kun se on päällä, määrittelee tavan, jolla se kiinnittyy debuggeriin. Se joko paljastaa prosessin address-vaihtoehdossa määritellyn osoitteen kautta. Muussa tapauksessa JDWP paljastaa oletusarvoisen
  • suspend määrittelee, pitäisikö JVM:n keskeyttää ja odottaa debuggerin kiinnittymistä vai ei
  • address on vaihtoehto, joka sisältää debuggerin paljastaman osoitteen, yleensä portin. Se voi myös edustaa osoitetta, joka on käännetty merkkijonoksi (kuten javadebug, jos käytämme server=y antamatta osoitetta Windowsissa)

3.2. Käynnistyskomento

Aloitetaan käynnistämällä etäsovellus. Annamme kaikki aiemmin luetellut vaihtoehdot:

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

Javaan 5 asti JVM-argumenttia runjdwp piti käyttää yhdessä toisen vaihtoehdon debug kanssa:

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

Tämä tapa käyttää JDWP:tä on edelleen tuettu, mutta siitä luovutaan tulevissa versioissa. Suosimme uudemman merkintätavan käyttöä aina kun se on mahdollista.

3.3. Java 9:stä lähtien

Loppujen lopuksi yksi JDWP:n vaihtoehdoista on muuttunut Javan version 9 julkaisun myötä. Tämä on melko pieni muutos, koska se koskee vain yhtä vaihtoehtoa, mutta sillä on merkitystä, jos yritämme debugata etäsovellusta.

Tämä muutos vaikuttaa siihen, miten osoite käyttäytyy etäsovelluksissa. Vanhempi merkintä address=8000 koskee vain localhostia. Saadaksemme aikaan vanhan käyttäytymisen, käytämme osoitteen etuliitteenä tähteä kaksoispisteen kanssa (esim. address=*:8000).

Dokumentaation mukaan tämä ei ole turvallista ja on suositeltavaa määrittää debuggerin IP-osoite aina kun se on mahdollista:

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

JDB: The Java Debugger

JDB eli Java Debugger on JDK:n mukana tuleva työkalu, jonka tarkoituksena on tarjota kätevä debuggeriohjelma-asiakas komentoriviltä käsin.

Käynnistääksemme JDB:n käytämme Attach-tilaa. Tämä tila liittää JDB:n käynnissä olevaan JVM:ään. Muitakin suoritustiloja on olemassa, kuten listen tai run, mutta ne ovat lähinnä käteviä, kun debugataan paikallisesti käynnissä olevaa sovellusta:

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

4.1. Taukopisteet

Jatketaan laittamalla joitakin taukopisteitä kappaleessa 1 esitettyyn sovellukseen.

Asetetaan taukopiste konstruktoriin:

> stop in OurApplication.<init>

Asetetaan toinen taukopiste staattiseen metodiin main, jossa käytetään String-luokan täyttä nimeä:

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

Viimeiseksi asetetaan viimeinen taukopiste instanssimetodiin buildInstanceString:

> stop in OurApplication.buildInstanceString(int)

Meidän pitäisi nyt huomata palvelinsovelluksen pysähtyvän ja seuraavan tulostuvan debuggerin konsoliin:

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

Lisätään nyt pysäytyspiste tietylle riville, sille, jossa muuttuja app.instanceString tulostuu:

> stop at OurApplication:7

Huomaamme, että stopin jälkeen käytetään in:n sijasta at:a, kun taukopiste määritellään tietylle riville.

4.2. Navigoi ja arvioi

Nyt kun olemme asettaneet taukopisteemme, jatketaan cont:n avulla säikeemme suoritusta, kunnes saavumme rivillä 7 olevaan taukopisteeseen.

Konsoliin pitäisi näkyä tulostettuna seuraava:

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

Muistutuksena, olemme pysähtyneet riville, joka sisältää seuraavan koodinpätkän:

System.out.println(app.instanceString);

Tälle riville pysähtyminen olisi voitu tehdä myös pysähtymällä main-metodin kohdalle ja kirjoittamalla step kahdesti. step suorittaa nykyisen koodirivin ja pysäyttää debuggerin suoraan seuraavalle riville.

Nyt kun olemme pysähtyneet, debuggeri arvioi staticString-merkkimme, sovelluksen instanceString-merkkimme, paikallisen muuttujan i ja lopuksi tarkastelee, miten muut lausekkeet arvioidaan.

Tulostetaan staticField konsoliin:

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

Me laitamme eksplisiittisesti luokan nimen static-kentän eteen.

Tulostetaan nyt appin instanssikenttä:

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

Seuraavaksi tarkastellaan muuttujaa i:

> print ii = 68741

Lokaalit muuttujat eivät vaadi muiden muuttujien tapaan luokan tai instanssin määrittämistä. Näemme myös, että print käyttäytyy täsmälleen samalla tavalla kuin eval: molemmat evaluoivat lausekkeen tai muuttujan.

Arvioidaan uusi OurApplicationin instanssi, jolle olemme antaneet konstruktorin parametrina kokonaisluvun:

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

Nyt kun olemme evaluoineet kaikki tarvitsemamme muuttujat, haluamme poistaa aiemmin asetetut pysäytyspisteet ja päästää säikeen jatkamaan käsittelyä. Tätä varten käytämme komentoa clear, jota seuraa taukopisteen tunniste.

Tunniste on täsmälleen sama kuin aiemmin komennolla stop käytetty tunniste:

> clear OurApplication:7Removed: breakpoint OurApplication:7

Varmistaaksemme, onko taukopiste poistettu oikein, käytämme komentoa clear ilman argumentteja. Tämä näyttää luettelon olemassa olevista taukopisteistä ilman juuri poistamaamme:

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

Johtopäätös

I:n tässä pika-artikkelissa olemme saaneet selville, miten JDWP:tä käytetään yhdessä JDB:n kanssa, jotka ovat molemmat JDK:n työkaluja.