Obsah
1. Pohled pod kapotu JVM – připojení debuggeru k běžícímu virtuálnímu stroji přes rozhraní JDI (2)
2. Připojení debuggeru k cílové JVM
3. Získání konektoru, který pro připojení k JVM využívá sockety
4. Připojení debuggeru k běžícímu virtuálnímu stroji přes socket
5. Nastavení parametrů JDI konektoru
6. Metody connectToVirtualMachine a shutdownVirtualMachine
7. Výpis základních informací o JVM, k níž se debugger připojil
8. Výpis informací o vláknech existujících ve cílové JVM
9. Základní informace o vláknech, které lze přes JDI získat
10. Výpis informací o třídách načtených do cílové JVM
11. Kompletní zdrojový kód demonstračního příkladu JDIVirtualMachineInfo
12. Překlad a spuštění demonstračního příkladu
13. Zdrojové kódy demonstračního příkladu i podpůrných skriptů
1. Pohled pod kapotu JVM – připojení debuggeru k běžícímu virtuálnímu stroji přes rozhraní JDI (2)
V dnešní části seriálu o programovacím jazyku Java i o virtuálním stroji Javy se již počtvrté vrátíme k popisu rozhraní JDI (Java Debugger Interface). Již v předchozích dílech jsme si poměrně dopodrobna popsali dva základní způsoby připojení debuggeru či jiného podobného nástroje k takzvanému sledovanému (cílovému) virtuálnímu stroji Javy. Nyní se již k tomuto tématu nebudeme vracet a budeme předpokládat, že je cílová JVM vždy spuštěna takovým způsobem, aby se k ní mohl debugger připojit přes sockety. Konkrétně to znamená, že cílová JVM bude spuštěna následujícím způsobem (důležité je zde především zvolené číslo portu, které musí znát i debugger):
java -agentlib:jdwp=transport=dt_socket,server=y,address=6502,suspend=y Test
Dnes si řekneme, jak lze s využitím metod nabízených rozhraním com.sun.jdi.VirtualMachine získat základní informace o cílové JVM. Zajímat nás budou zpočátku informace o vláknech vytvořených v cílové JVM (ať již se jedná o vlákna běžící či naopak o vlákna pozastavená) i informace o třídách, které byly do tohoto virtuálního stroje Javy načteny. Podobně, jako tomu bylo v předchozí části tohoto seriálu, se ukáže, že javovské rozhraní JDI je na použití mnohem jednodušší, než nativní rozhraní JVM TI.
2. Připojení debuggeru k cílové JVM
Podobně jako minule, i dnes si funkce nabízené rozhraním JDI popíšeme a využijeme v relativně jednoduchém demonstračním příkladu, jehož úplný zdrojový kód najdete v jedenácté kapitole. Tento demonstrační příklad představuje kostru debuggeru, který se k cílovému virtuálnímu stroji Javy připojuje přes sockety. To znamená, že je nejprve nutné získat všechny nabízené konektory implementující rozhraní com.sun.jdi.connect.AttachingConnector s využitím metody VirtualMachineManager.AttachingConnector() a posléze je nutné z tohoto seznamu vybrat jediný konektor umožňující spojení s cílovou JVM přes sockety – samotný výběr konektoru zajišťuje uživatelská metoda getSocketAttachConnector() vypsaná v následující kapitole. Jakmile se příslušný konektor získá, zavolá se další uživatelská metoda nazvaná debugVirtualMachineUsingSocket(), jejímž popisem se budeme zabývat v kapitole čtvrté:
public static void main(String[] args) { // ziskat (jedinou) instanci tridy VirtualMachineManager VirtualMachineManager virtualMachineManager = Bootstrap.virtualMachineManager(); // ziskat vsechny konektory pouzite pro pripojeni k bezici JVM List<AttachingConnector> connectors = virtualMachineManager.attachingConnectors(); // potrebujeme ziskat konektor pouzivajici pro pripojeni sockety AttachingConnector connector = getSocketAttachConnector(connectors); if (connector == null) { System.out.println("Socket connector is not available"); return; } debugVirtualMachineUsingSocket(connector); }
3. Získání konektoru, který pro připojení k JVM využívá sockety
Metoda getSocketAttachConnector(), která je volána přímo z metody main() [kapitola 2], je implementována sice jednoduše, ovšem (alespoň zdánlivě) neefektivně. Porovnáváme zde totiž jména jednotlivých konektorů s řetězcovou konstantou (literálem) a pokud je nalezen konektor se jménem „com.sun.jdi.SocketAttach“ (tento řetězec je uložen v konstantě SOCKET_ATTACH_CONNECTOR_NAME), je tento konektor metodou ihned vrácen. Na tomto místě je vhodné poznamenat, že vzhledem k současnému stavu JDI zde není možné použít například operátor instanceof, protože konkrétní jméno třídy či rozhraní, kterou musí konektor typu „SocketAttach“ implementovat, není přesně zdokumentováno a stabilizováno mezi různými implementacemi JVM (poznámka: zde popisovaná metoda je shodná se stejně pojmenovanou metodou použitou v předchozí části tohoto seriálu):
/** * Ziskat konektor pouzivajici pro pripojeni sockety */ private static AttachingConnector getSocketAttachConnector(List<AttachingConnector> connectors) { for (AttachingConnector connector : connectors) { if (SOCKET_ATTACH_CONNECTOR_NAME.equals(connector.name())) { return connector; } } return null; }
4. Připojení debuggeru k běžícímu virtuálnímu stroji přes socket
Dostáváme se k metodě debugVirtualMachineUsingSocket(), která tvoří ústřední část našeho zatím stále ještě velmi jednoduchého debuggeru. V této metodě se nejprve přes uživatelskou metodu prepareConnectorArguments() [kapitola 5] nastaví všechny argumenty používané konektorem při připojování k cílovému virtuálnímu stroji Javy, provede se vlastní připojení v další uživatelské metodě connectToVirtualMachine() [kapitola 6] a posléze se na standardní výstup vytisknou všechny informace získané z cílové JVM. Následně je cílová JVM zastavena v metodě shutdownVirtualMachine() [kapitola 6], a to především z toho důvodu, že by bylo poměrně obtížné navázat s cílovou JVM další spojení ve chvíli, kdy se již jeden debugger odpojil.
/** * Pripojeni k bezicimu virtualnimu stroji pres socket. * @throws InterruptedException */ private static void debugVirtualMachineUsingSocket(AttachingConnector connector) { // nastaveni argumentu pouzivanych konektorem Map<String, Connector.Argument> arguments = prepareConnectorArguments(connector); try { // pripojeni ke vzdalenemu bezicimu virtualnimu stroji Javy VirtualMachine virtualMachine = connectToVirtualMachine(connector, arguments); // vypis zakladnich informaci o pripojenem VM printVirtualMachineInfo(virtualMachine); // ukonceni behu vzdaleneho virtualniho stroje shutdownVirtualMachine(virtualMachine); } catch (IOException e) { e.printStackTrace(); } catch (IllegalConnectorArgumentsException e) { e.printStackTrace(); } }
5. Nastavení parametrů JDI konektoru
Ve chvíli, kdy již máme vybrán správný konektor, je možné se připojit k běžícímu virtuálnímu stroji Javy. Aby se připojení skutečně provedlo, musí se nejprve nastavit port, přes který se debugger k cílové JVM připojí. My jsme si zvolili port číslo 6502 a nastavení příslušného parametru (či přesněji řečeno argumentu) konektoru je provedeno v samostatné uživatelské metodě nazvané prepareConnectorArguments(). Tato metoda pracuje velmi jednoduše – nejprve přečte všechny výchozí argumenty konektoru a posléze změní argument uložený pod klíčem „port“. Připomeňme si, že mapa vrácená přes AttachingConnector.defaultArguments() je neměnitelná v tom smyslu, že do ní nelze přidávat či z ní odebírat další prvky, ovšem hodnoty jednotlivých prvků je samozřejmě možné měnit:
/** * Nastaveni portu na cilove JVM, jenz debugger pouzije pro navazani spojeni. * * @param connector konektor pouzity pro pripojeni * @return mapa obsahujici parametry konektoru */ private static Map<String, Connector.Argument> prepareConnectorArguments(AttachingConnector connector) { Map<String, Connector.Argument> arguments = connector.defaultArguments(); arguments.get("port").setValue("6502"); return arguments; }
6. Metody connectToVirtualMachine a shutdownVirtualMachine
V této kapitole si stručně popíšeme význam dvou pomocných uživatelských metod nazvaných connectToVirtualMachine a shutdownVirtualMachine. První z těchto metod slouží k připojení k cílovému virtuálnímu stroji Javy, což je uvnitř metody zajištěno voláním AttachingConnector.attach(), kterému se předá mapa obsahující nové argumenty (parametry) konektoru. V našem demonstračním debuggeru je modifikován argument „port“ v již popsané metodě prepareConnectorArguments() [kapitola 5]:
/** * Pripojeni debuggeru ke sledovanemu virtualnimu stroji. * * @param connector konektor vyuzivajici pro spojeni sockety * @param arguments mapa obsahujici parametry pripojeni * @return sledovany virtualni stroj, k nemuz je debugger vzdalene pripojen * * @throws IOException vyvolane v pripade, ze se pripojeni k JVM nepodari * @throws IllegalConnectorArgumentsException vyvolane v pripade spatne zadanych parametru */ private static VirtualMachine connectToVirtualMachine(AttachingConnector connector, Map<String, Connector.Argument> arguments) throws IOException, IllegalConnectorArgumentsException { System.out.println("Connecting to virtual machine"); VirtualMachine virtualMachine = connector.attach(arguments); System.out.println("Connected"); return virtualMachine; }
Další pomocná metoda nazvaná shutdownVirtualMachine() slouží pro násilné ukončení cílové JVM s předáním návratového kódu, který je následně vrácen operačnímu systému:
/** * Ukonceni prace beziciho sledovaneho virtualniho stroje. * * @param virtualMachine sledovany virtualni stroj, k nemuz je debugger vzdalene pripojen */ private static void shutdownVirtualMachine(VirtualMachine virtualMachine) { System.out.println("Calling exit"); virtualMachine.exit(EXIT_VALUE); }
7. Výpis základních informací o JVM, k níž se debugger připojil
Jakmile se náš demonstrační debugger připojí k cílovému virtuálnímu stroji Javy, je možné s využitím rozhraní com.sun.jdi.VirtualMachine získat mnoho důležitých informací o sledované JVM. Všechny informace jsou získávány a vypisovány v uživatelské metodě nazvané printVirtualMachineInfo(), z níž se volá čtveřice dalších uživatelských metod printDescription(), printVersion(), printThreadInfo() a printClassesInfo():
/** * Vypis informaci ziskanych ze sledovaneho virtualniho stroje. * * @param virtualMachine sledovany virtualni stroj, k nemuz je debugger vzdalene pripojen */ private static void printVirtualMachineInfo(VirtualMachine virtualMachine) { System.out.println("Basic virtual machine info:"); printDescription(virtualMachine); printVersion(virtualMachine); printThreadInfo(virtualMachine); printClassesInfo(virtualMachine); }
Zajímavá je metoda printDescription(), která na standardní výstup vypíše podrobnější víceřádkový popis propojení mezi cílovým JVM a debuggerem. Pro tento účel se uvnitř této metody volá bezparametrická metoda VirtualMachine.description() vracející řetězec:
/** * Slovni viceradkovy popis sledovaneho virtualniho stroje. * * @param virtualMachine sledovany virtualni stroj, k nemuz je debugger vzdalene pripojen */ private static void printDescription(VirtualMachine virtualMachine) { String description = virtualMachine.description(); System.out.println("Description: " + description); System.out.println(); }
Zpráva získaná touto metodou může vypadat například takto:
Java Debug Interface (Reference Implementation) version 1.6 Java Debug Wire Protocol (Reference Implementation) version 1.6 JVM Debug Interface version 1.1 JVM version 1.6.0_01 (Java HotSpot(TM) Client VM, mixed mode, sharing)
Další metoda vypíše verzi cílového virtuálního stroje Javy, kterou je možné použít například pro rozhodování, které informace je možné z této JVM získat. Uvnitř je využito volání bezparametrické metody VirtualMachine.version() vracející řetězec (nikoli celé číslo!):
/** * Verze sledovaneho virtualniho stroje. * * @param virtualMachine sledovany virtualni stroj, k nemuz je debugger vzdalene pripojen */ private static void printVersion(VirtualMachine virtualMachine) { String version = virtualMachine.version(); System.out.println("Version: " + version); System.out.println(); }
Zpráva vypsaná touto metodou může vypadat takto:
Version: 1.6.0_01
8. Výpis informací o vláknech existujících ve cílové JVM
S využitím metod dostupných přes rozhraní com.sun.jdi.VirtualMachine je možné přečíst i informace o všech vláknech vytvořených ve sledovaném virtuálním stroji Javy. Jedná se o vlákna, která se mohou nacházet v libovolném stavu, tj. o vlákna běžící, pozastavená, ještě nespuštěná, vlákna čekající na uvolnění zámku atd. atd. Seznam všech vláken se přečte metodou VirtualMachine.allThreads(), přičemž prvky tohoto seznamu jsou typu com.sun.jdi.ThreadReference. Každý objekt tohoto typu obsahuje mnoho informací o jednotlivých vláknech, například unikátní identifikátor vlákna, jeho jméno, stav vlákna a taktéž informaci o tom, zda je vlákno pozastaveno samotným debuggerem. O přečtení informací o všech vláknech a následný výpis těchto informací se stará uživatelská metoda nazvaná printThreadInfo():
/** * Vypis informaci o vlaknech existujicich ve sledovanem virtualnim stroji. * * @param virtualMachine sledovany virtualni stroj, k nemuz je debugger vzdalene pripojen */ private static void printThreadInfo(VirtualMachine virtualMachine) { System.out.println("Thread info:"); System.out.println(" UniqueID Thread name Status Suspended"); List<ThreadReference> threads = virtualMachine.allThreads(); for (ThreadReference thread : threads) { String threadName = thread.name(); String threadStatus = getThreadStatus(thread); String threadSuspended = getThreadSuspended(thread); long uniqueID = thread.uniqueID(); System.out.format(" %8d %-20s %-12s %-5s\n", uniqueID, threadName, threadStatus, threadSuspended); } System.out.println(); }
Použité metody:
String com.sun.jdi.ThreadReference.name() long com.sun.jdi.ObjectReference.uniqueID() - zděděno int com.sun.jdi.ThreadReference.status() boolean com.sun.jdi.ThreadReference.isSuspended()
9. Základní informace o vláknech, které lze přes JDI získat
Uživatelská metoda printThreadInfo() popsaná v předchozí kapitole ve skutečnosti volá dvě další uživatelské metody nazvané getThreadStatus() a getThreadSuspended(). Metoda getThreadStatus() vrací stav vlákna jako řetězec. Ve skutečnosti je totiž stav vlákna reprezentován celočíselnou konstantou (nikoli plnohodnotným výčtovým typem), který lze na řetězec převést například s využitím strukturovaného příkazu switch (osobně si myslím, že by tento příkaz nemusel být v objektově orientovaném jazyce vůbec implementován, ovšem jeho interní implementace je v případě celočíselných konstant poměrně efektivní, a to jak po paměťové stránce, tak po stránce efektivity vygenerovaného bajtkódu):
/** * Prevod stavu vlakna na retezec. * * @param thread JDI objekt predstavujici vlakno * @return stav vlakna v retezcove podobe */ private static String getThreadStatus(ThreadReference thread) { switch (thread.status()) { case ThreadReference.THREAD_STATUS_NOT_STARTED: return "not started"; case ThreadReference.THREAD_STATUS_RUNNING: return "running"; case ThreadReference.THREAD_STATUS_SLEEPING: return "sleeping"; case ThreadReference.THREAD_STATUS_MONITOR: return "wait/monitor"; case ThreadReference.THREAD_STATUS_WAIT: return "Object.wait"; case ThreadReference.THREAD_STATUS_ZOMBIE: return "zombie"; case ThreadReference.THREAD_STATUS_UNKNOWN: return "*unkwnown*"; default: return "should not happen!"; } }
Další pomocná uživatelská metoda nazvaná getThreadSuspended() je velmi jednoduchá – vrací pouze řetězec „yes“ či „no“ na základě toho, zda je dané vlákno v cílovém virtuálním stroji Javy pozastaveno debuggerem či nikoli (tento stav je nutné odlišit od původního stavu vlákna):
/** * Informace (ve tvaru retezce) o tom, zda je vlakno pozastaveno ci nikoli. * * @param thread JDI objekt predstavujici vlakno * @return stav pozastaveni vlakna v retezcove podobe */ private static String getThreadSuspended(ThreadReference thread) { return thread.isSuspended() ? "yes" : "no"; }
10. Výpis informací o třídách načtených do cílové JVM
Mezi další důležité informace o cílovém virtuálním stroji Javy patří seznam tříd, které jsou do této JVM načteny jak při inicializaci virtuálního stroje, tak i při běhu aplikace. Všechny dostupné třídy lze získat s využitím metody com.sun.jdi.VirtualMachine.allClasses() vracející seznam objektů typu com.sun.jdi.ReferenceType, což je třída odvozená od obecnější třídy com.sun.jdi.Type, s níž se pravděpodobně seznámíme příště. O každé třídě lze získat mnoho informací, samozřejmě včetně jejího jména a signatury. Aby nebyl výpis příliš dlouhý, omezíme se v našem testovacím příkladu na výpis tříd obsahujících řetězec „String“:
/** * Vypis zakladnich informaci o tridach nactenych do sledovane JVM. * * @param virtualMachine sledovany virtualni stroj, k nemuz je debugger vzdalene pripojen */ private static void printClassesInfo(VirtualMachine virtualMachine) { System.out.println("Class info:"); System.out.format(" %-45s %-45s %s\n", "Name", "Signature", "Source"); List<ReferenceType> classes = virtualMachine.allClasses(); for (ReferenceType klass : classes) { String className = klass.name(); /* jednoduchy filtr */ if (!className.contains("String")) continue; String classSignature = klass.signature(); String sourceName; try { sourceName = klass.sourceName(); } catch (AbsentInformationException e) { sourceName = "unknown"; } System.out.format(" %-45s %-45s %s\n", className, classSignature, sourceName); } System.out.println(); }
Použité metody:
String com.sun.jdi.ReferenceType.name() String com.sun.jdi.ReferenceType.sourceName() String com.sun.jdi.Type.signature() - zděděno
11. Kompletní zdrojový kód demonstračního příkladu JDIVirtualMachineInfo
V předchozích kapitolách byly popsány prakticky všechny metody, které jsou součástí dnešního demonstračního příkladu tvořícího kostru debuggeru. Pod tímto odstavcem je vypsán celý zdrojový kód tohoto velmi jednoduchého debuggeru:
import java.io.IOException; import java.util.List; import java.util.Map; import com.sun.jdi.AbsentInformationException; import com.sun.jdi.Bootstrap; import com.sun.jdi.IncompatibleThreadStateException; import com.sun.jdi.ReferenceType; import com.sun.jdi.ThreadReference; import com.sun.jdi.VirtualMachine; import com.sun.jdi.VirtualMachineManager; import com.sun.jdi.connect.AttachingConnector; import com.sun.jdi.connect.Connector; import com.sun.jdi.connect.IllegalConnectorArgumentsException; /** * Pripojeni k bezicimu virtualnimu stroji Javy, * ktery byl spusten s parametry: * java -agentlib:jdwp=transport=dt_socket,server=y,address=6502,suspend=y Trida * * @author Pavel Tisnovsky */ public class JDIVirtualMachineInfo { /** * Navratovy kod pouzity pri ukoncovani sledovane JVM. */ private static final int EXIT_VALUE = 0; /** * Jmeno konektoru, ktery pro pripojeni pouziva sockety. */ private static final String SOCKET_ATTACH_CONNECTOR_NAME = "com.sun.jdi.SocketAttach"; public static void main(String[] args) { // ziskat (jedinou) instanci tridy VirtualMachineManager VirtualMachineManager virtualMachineManager = Bootstrap.virtualMachineManager(); // ziskat vsechny konektory pouzite pro pripojeni k bezici JVM List<AttachingConnector> connectors = virtualMachineManager.attachingConnectors(); // potrebujeme ziskat konektor pouzivajici pro pripojeni sockety AttachingConnector connector = getSocketAttachConnector(connectors); if (connector == null) { System.out.println("Socket connector is not available"); return; } debugVirtualMachineUsingSocket(connector); } /** * Ziskat konektor pouzivajici pro pripojeni sockety */ private static AttachingConnector getSocketAttachConnector(List<AttachingConnector> connectors) { for (AttachingConnector connector : connectors) { if (SOCKET_ATTACH_CONNECTOR_NAME.equals(connector.name())) { return connector; } } return null; } /** * Pripojeni k bezicimu virtualnimu stroji pres socket. * @throws InterruptedException */ private static void debugVirtualMachineUsingSocket(AttachingConnector connector) { // nastaveni argumentu pouzivanych konektorem Map<String, Connector.Argument> arguments = prepareConnectorArguments(connector); try { // pripojeni ke vzdalenemu bezicimu virtualnimu stroji Javy VirtualMachine virtualMachine = connectToVirtualMachine(connector, arguments); // vypis zakladnich informaci o pripojenem VM printVirtualMachineInfo(virtualMachine); // ukonceni behu vzdaleneho virtualniho stroje shutdownVirtualMachine(virtualMachine); } catch (IOException e) { e.printStackTrace(); } catch (IllegalConnectorArgumentsException e) { e.printStackTrace(); } } /** * Nastaveni portu na cilove JVM, jenz debugger pouzije pro navazani spojeni. * * @param connector konektor pouzity pro pripojeni * @return mapa obsahujici parametry konektoru */ private static Map<String, Connector.Argument> prepareConnectorArguments(AttachingConnector connector) { Map<String, Connector.Argument> arguments = connector.defaultArguments(); arguments.get("port").setValue("6502"); return arguments; } /** * Pripojeni debuggeru ke sledovanemu virtualnimu stroji. * * @param connector konektor vyuzivajici pro spojeni sockety * @param arguments mapa obsahujici parametry pripojeni * @return sledovany virtualni stroj, k nemuz je debugger vzdalene pripojen * * @throws IOException vyvolane v pripade, ze se pripojeni k JVM nepodari * @throws IllegalConnectorArgumentsException vyvolane v pripade spatne zadanych parametru */ private static VirtualMachine connectToVirtualMachine(AttachingConnector connector, Map<String, Connector.Argument> arguments) throws IOException, IllegalConnectorArgumentsException { System.out.println("Connecting to virtual machine"); VirtualMachine virtualMachine = connector.attach(arguments); System.out.println("Connected"); return virtualMachine; } /** * Ukonceni prace beziciho sledovaneho virtualniho stroje. * * @param virtualMachine sledovany virtualni stroj, k nemuz je debugger vzdalene pripojen */ private static void shutdownVirtualMachine(VirtualMachine virtualMachine) { System.out.println("Calling exit"); virtualMachine.exit(EXIT_VALUE); } /** * Vypis informaci ziskanych ze sledovaneho virtualniho stroje. * * @param virtualMachine sledovany virtualni stroj, k nemuz je debugger vzdalene pripojen */ private static void printVirtualMachineInfo(VirtualMachine virtualMachine) { System.out.println("Basic virtual machine info:"); printDescription(virtualMachine); printVersion(virtualMachine); printThreadInfo(virtualMachine); printClassesInfo(virtualMachine); } /** * Slovni viceradkovy popis sledovaneho virtualniho stroje. * * @param virtualMachine sledovany virtualni stroj, k nemuz je debugger vzdalene pripojen */ private static void printDescription(VirtualMachine virtualMachine) { String description = virtualMachine.description(); System.out.println("Description: " + description); System.out.println(); } /** * Verze sledovaneho virtualniho stroje. * * @param virtualMachine sledovany virtualni stroj, k nemuz je debugger vzdalene pripojen */ private static void printVersion(VirtualMachine virtualMachine) { String version = virtualMachine.version(); System.out.println("Version: " + version); System.out.println(); } /** * Vypis informaci o vlaknech existujicich ve sledovanem virtualnim stroji. * * @param virtualMachine sledovany virtualni stroj, k nemuz je debugger vzdalene pripojen */ private static void printThreadInfo(VirtualMachine virtualMachine) { System.out.println("Thread info:"); System.out.println(" UniqueID Thread name Status Suspended"); List<ThreadReference> threads = virtualMachine.allThreads(); for (ThreadReference thread : threads) { String threadName = thread.name(); String threadStatus = getThreadStatus(thread); String threadSuspended = getThreadSuspended(thread); long uniqueID = thread.uniqueID(); System.out.format(" %8d %-20s %-12s %-5s\n", uniqueID, threadName, threadStatus, threadSuspended); } System.out.println(); } /** * Prevod stavu vlakna na retezec. * * @param thread JDI objekt predstavujici vlakno * @return stav vlakna v retezcove podobe */ private static String getThreadStatus(ThreadReference thread) { switch (thread.status()) { case ThreadReference.THREAD_STATUS_NOT_STARTED: return "not started"; case ThreadReference.THREAD_STATUS_RUNNING: return "running"; case ThreadReference.THREAD_STATUS_SLEEPING: return "sleeping"; case ThreadReference.THREAD_STATUS_MONITOR: return "wait/monitor"; case ThreadReference.THREAD_STATUS_WAIT: return "Object.wait"; case ThreadReference.THREAD_STATUS_ZOMBIE: return "zombie"; case ThreadReference.THREAD_STATUS_UNKNOWN: return "*unkwnown*"; default: return "should not happen!"; } } /** * Informace (ve tvaru retezce) o tom, zda je vlakno pozastaveno ci nikoli. * * @param thread JDI objekt predstavujici vlakno * @return stav pozastaveni vlakna v retezcove podobe */ private static String getThreadSuspended(ThreadReference thread) { return thread.isSuspended() ? "yes" : "no"; } /** * Vypis zakladnich informaci o tridach nactenych do sledovane JVM. * * @param virtualMachine sledovany virtualni stroj, k nemuz je debugger vzdalene pripojen */ private static void printClassesInfo(VirtualMachine virtualMachine) { System.out.println("Class info:"); System.out.format(" %-45s %-45s %s\n", "Name", "Signature", "Source"); List<ReferenceType> classes = virtualMachine.allClasses(); for (ReferenceType klass : classes) { String className = klass.name(); /* jednoduchy filtr */ if (!className.contains("String")) continue; String classSignature = klass.signature(); String sourceName; try { sourceName = klass.sourceName(); } catch (AbsentInformationException e) { sourceName = "unknown"; } System.out.format(" %-45s %-45s %s\n", className, classSignature, sourceName); } System.out.println(); } }
12. Překlad a spuštění demonstračního příkladu
Překlad demonstračního příkladu zajistí následující příkaz, který byl odladěn na OpenJDK6. Pokud je využita jiná JDK či odlišná verze OpenJDK, je nutné upravit cestu k java archivu tools.jar:
javac -classpath /usr/lib/jvm/java-6-openjdk/lib/tools.jar:. JDIVirtualMachineInfo.java
Pro kontrolu funkčnosti našeho demonstračního příkladu se použije následující testovací třída (stejná třída byla použita i minule):
public class Test { public static void main(String[] args) { while (true) { System.out.println("Hello world!"); } } }
Po překladu testovací třídy:
javac Test
se provede její spuštění následujícím způsobem:
java -agentlib:jdwp=transport=dt_socket,server=y,address=6502,suspend=y Test
Demonstrační debugger, který byl popsaný v předchozích kapitolách, je nutné spustit tak, aby virtuální stroj Javy našel i archiv tools.jar:
java -cp /usr/lib/jvm/java-6-openjdk/lib/tools.jar:. JDIVirtualMachineInfo
Po spuštění by se měla na standardním výstupu objevit přibližně tato zpráva (její konkrétní znění se může lišit v závislosti na tom, jaká JRE je použita pro spuštění cílové JVM):
Connecting to virtual machine Connected Basic virtual machine info: Description: Java Debug Interface (Reference Implementation) version 1.6 Java Debug Wire Protocol (Reference Implementation) version 1.6 JVM Debug Interface version 1.1 JVM version 1.6.0_01 (Java HotSpot(TM) Client VM, mixed mode, sharing) Version: 1.6.0_01 Thread info: UniqueID Thread name Status Suspended 2 Attach Listener running yes 3 Signal Dispatcher running yes 4 Finalizer Object.wait yes 5 Reference Handler Object.wait yes 1 main running yes Class info: Name Signature Source java.lang.AbstractStringBuilder Ljava/lang/AbstractStringBuilder; AbstractStringBuilder.java java.lang.String Ljava/lang/String; String.java java.lang.String$CaseInsensitiveComparator Ljava/lang/String$CaseInsensitiveComparator; String.java java.lang.StringBuffer Ljava/lang/StringBuffer; StringBuffer.java java.lang.StringBuilder Ljava/lang/StringBuilder; StringBuilder.java java.lang.StringCoding Ljava/lang/StringCoding; StringCoding.java java.lang.StringCoding$StringDecoder Ljava/lang/StringCoding$StringDecoder; StringCoding.java java.lang.StringCoding$StringEncoder Ljava/lang/StringCoding$StringEncoder; StringCoding.java java.lang.String[] [Ljava/lang/String; unknown java.util.StringTokenizer Ljava/util/StringTokenizer; StringTokenizer.java Calling exit
13. Zdrojové kódy demonstračního příkladu i podpůrných skriptů
Zdrojové kódy demonstračního příkladu, testovací třídy i skriptů použitých pro překlad a spuštění demonstračního příkladu, byly uloženy do Mercurial repositáře dostupného na adrese http://icedtea.classpath.org/people/ptisnovs/jvm-tools/. Prozatím nejnovější verze všech zmíněných zdrojových souborů a skriptů můžete najít na adresách:
# | Zdrojový soubor/skript | Umístění v repositáři |
---|---|---|
1 | JDIVirtualMachineInfo | http://icedtea.classpath.org/people/ptisnovs/jvm-tools/file/cadf0d815a2b/jdi/JDIVirtualMachineInfo.java |
2 | compile.sh | http://icedtea.classpath.org/people/ptisnovs/jvm-tools/file/cadf0d815a2b/jdi/compile.sh |
3 | Test.java | http://icedtea.classpath.org/people/ptisnovs/jvm-tools/file/716580c5d0ab/jdi/Test.java |
4 | Test.sh | http://icedtea.classpath.org/people/ptisnovs/jvm-tools/file/716580c5d0ab/jdi/Test.sh |
14. Odkazy na Internetu
- Breakpoint (Wikipedia)
http://cs.wikipedia.org/wiki/Breakpoint - JVM Tool Interface Version 1.2 Documentation
http://docs.oracle.com/javase/7/docs/platform/jvmti/jvmti.html - JVM Tool Interface Version 1.2 Documentation – SetBreakpoint
http://docs.oracle.com/javase/6/docs/platform/jvmti/jvmti.html#SetBreakpoint - JVM Tool Interface Version 1.2 Documentation – ClearBreakpoint
http://docs.oracle.com/javase/6/docs/platform/jvmti/jvmti.html#ClearBreakpoint - JVM Tool Interface Version 1.2 Documentation – Breakpoint (callback funkce)
http://docs.oracle.com/javase/6/docs/platform/jvmti/jvmti.html#Breakpoint - The JVM Tool Interface (JVM TI): How VM Agents Work
http://www.oracle.com/technetwork/articles/javase/jvm-ti-141370.html - Creating a Debugging and Profiling Agent with JVMTI
http://www.oracle.com/technetwork/articles/javase/jvmti-136367.html - JVM TI (Wikipedia)
http://en.wikipedia.org/wiki/JVM_TI - IBM JVMTI extensions
http://publib.boulder.ibm.com/infocenter/realtime/v2r0/index.jsp?topic=%2Fcom.ibm.softrt.doc%2Fdiag%2Ftools%2Fjvmti_extensions.html - An empirical study of Java bytecode programs
http://www.mendeley.com/research/an-empirical-study-of-java-bytecode-programs/ - Java quick guide: JVM Instruction Set (tabulka všech instrukcí JVM)
http://www.mobilefish.com/tutorials/java/java_quickguide_jvm_instruction_set.html - The JVM Instruction Set
http://mpdeboer.home.xs4all.nl/scriptie/node14.html - Control Flow in the Java Virtual Machine
http://www.artima.com/underthehood/flowP.html - The class File Format
http://java.sun.com/docs/books/jvms/second_edition/html/ClassFile.doc.html - javap – The Java Class File Disassembler
http://docs.oracle.com/javase/1.4.2/docs/tooldocs/windows/javap.html - javap-java-1.6.0-openjdk(1) – Linux man page
http://linux.die.net/man/1/javap-java-1.6.0-openjdk - Using javap
http://www.idevelopment.info/data/Programming/java/miscellaneous_java/Using_javap.html - Examine class files with the javap command
http://www.techrepublic.com/article/examine-class-files-with-the-javap-command/5815354 - Bytecode Engineering
http://book.chinaunix.net/special/ebook/Core_Java2_Volume2AF/0131118269/ch13lev1sec6.html