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.
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.
.
Napsat komentář