Přehled

Debugování vzdálené Java aplikace se může hodit v nejednom případě.

V tomto tutoriálu zjistíme, jak na to pomocí nástrojů JDK.

Aplikace

Začneme napsáním aplikace. Spustíme ji na vzdáleném místě a budeme ji ladit lokálně prostřednictvím tohoto článku:

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

Java Debug Wire Protocol je protokol používaný v Javě pro komunikaci mezi ladicím programem a ladicím zařízením. Debuggee je laděná aplikace, zatímco debugger je aplikace nebo proces připojující se k laděné aplikaci.

Obě aplikace buď běží na stejném počítači, nebo na různých počítačích. My se zaměříme na ty druhé.

3.1. Možnosti JDWP

Při spouštění ladicí aplikace budeme v argumentech příkazového řádku JVM používat JDWP.

Jeho volání vyžaduje seznam možností:

  • transport je jediná plně povinná možnost. Určuje, který transportní mechanismus se má použít. dt_shmem funguje pouze v systému Windows a pokud oba procesy běží na stejném stroji, zatímco dt_socket je kompatibilní se všemi platformami a umožňuje, aby procesy běžely na různých strojích
  • server není povinná volba. Tento příznak, je-li zapnutý, určuje způsob připojení k ladicímu serveru. Buď vystaví proces prostřednictvím adresy definované v parametru address. V opačném případě JDWP vystaví výchozí
  • suspend definuje, zda se má JVM pozastavit a čekat na připojení ladicího programu, nebo ne
  • address je volba obsahující adresu, obvykle port, vystavenou ladicím programem. Může také představovat adresu přeloženou jako řetězec znaků (jako javadebug, pokud použijeme server=y bez uvedení adresy v systému Windows)

3.2. Příkaz ke spuštění

Začněme spuštěním vzdálené aplikace. Poskytneme všechny dříve uvedené možnosti:

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

Do Javy 5 bylo nutné použít argument JVM runjdwp spolu s další možností debug:

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

Tento způsob použití JDWP je stále podporován, ale v dalších verzích bude vypuštěn. Pokud to bude možné, budeme preferovat použití novějšího zápisu.

3.3. Od verze Java 9

S vydáním verze 9 Javy se konečně změnila jedna z možností JDWP. Jedná se o poměrně malou změnu, protože se týká pouze jedné možnosti, ale bude mít význam, pokud se snažíme ladit vzdálenou aplikaci.

Tato změna ovlivňuje způsob, jakým se adresa chová u vzdálených aplikací. Starší zápis address=8000 platí pouze pro localhost. Abychom dosáhli starého chování, použijeme hvězdičku s dvojtečkou jako předponu adresy (např. address=*:8000).

Podle dokumentace to není bezpečné a doporučuje se zadávat IP adresu debuggeru, kdykoli je to možné:

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

JDB: The Java Debugger

JDB, Java Debugger, je nástroj obsažený v JDK koncipovaný tak, aby poskytoval pohodlného klienta debuggeru z příkazového řádku.

Pro spuštění JDB použijeme režim připojení. Tento režim připojí JDB ke spuštěnému JVM. Existují i další režimy spouštění, například listen nebo run, ale jsou výhodné hlavně při ladění lokálně běžící aplikace:

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

4.1. Body přerušení

Pokračujme umístěním několika bodů přerušení do aplikace představené v části 1.

Nastavíme breakpoint na konstruktoru:

> stop in OurApplication.<init>

Další nastavíme ve statické metodě main, přičemž použijeme plně kvalifikovaný název třídy String:

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

Nakonec nastavíme poslední na instanční metodě buildInstanceString:

> stop in OurApplication.buildInstanceString(int)

Měli bychom si nyní všimnout, že se serverová aplikace zastaví a v konzoli našeho debuggeru se vypíše následující zpráva:

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

Přidáme nyní bod přerušení na konkrétní řádek, na ten, kde je proměnná app.instanceString vypisuje:

> stop at OurApplication:7

Všimneme si, že při definování bodu přerušení na konkrétním řádku se za stop používá at místo in.

4.2. Navigace a vyhodnocování

Teď, když jsme nastavili body přerušení, použijme cont, abychom pokračovali v provádění našeho vlákna, dokud nedosáhneme bodu přerušení na řádku 7.

V konzoli bychom měli vidět vypsáno následující:

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

Připomínáme, že jsme se zastavili na řádku obsahujícím následující část kódu:

System.out.println(app.instanceString);

Zastavení na tomto řádku jsme mohli provést také tak, že jsme se zastavili na metodě main a dvakrát napsali step. step provede aktuální řádek kódu a zastaví debugger přímo na dalším řádku.

Teď, když jsme se zastavili, debugee vyhodnocuje náš staticString, instanceString aplikace, lokální proměnnou i a nakonec se podívá na to, jak vyhodnotit další výrazy.

Vypíšeme staticField na konzoli:

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

Explicitně jsme před statické pole vložili název třídy.

Nyní vypíšeme pole instance app:

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

Dále se podíváme na proměnnou i:

> print ii = 68741

Na rozdíl od ostatních proměnných nevyžadují lokální proměnné uvedení třídy nebo instance. Vidíme také, že print má úplně stejné chování jako eval: obě vyhodnocují výraz nebo proměnnou.

Vyhodnotíme novou instanci OurApplication, které jsme jako parametr konstruktoru předali celé číslo:

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

Teď, když jsme vyhodnotili všechny proměnné, které jsme potřebovali, budeme chtít smazat dříve nastavené body přerušení a nechat vlákno pokračovat ve zpracování. Abychom toho dosáhli, použijeme příkaz clear následovaný identifikátorem bodu přerušení.

Identifikátor je přesně stejný jako ten, který jsme použili dříve u příkazu stop:

> clear OurApplication:7Removed: breakpoint OurApplication:7

Pro ověření, zda byl bod přerušení správně odstraněn, použijeme příkaz clear bez argumentů. Tím se zobrazí seznam existujících bodů přerušení bez toho, který jsme právě odstranili:

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

Závěr

I:V tomto krátkém článku jsme zjistili, jak používat JDWP společně s JDB, tedy oba nástroje JDK.

.