Obsah
1. Pohled pod kapotu JVM – přístup k zásobníkovým rámcům vláken sledované JVM přes rozhraní JDI
2. Získání a následný výpis obsahu zásobníkových rámců s využitím rozhraní JDI
3. Výpis informací o vybraném zásobníkovém rámci
4. Přečtení jména volané metody i jména třídy, v níž je tato metoda deklarována
5. Získání řádku, v níž došlo k volání metody a přečtení jména zdrojového souboru třídy
6. Kompletní zdrojový kód demonstračního příkladu JDIStackTraceList
7. Spuštění demonstračního příkladu
8. Zdrojové kódy demonstračního příkladu i podpůrných skriptů
1. Pohled pod kapotu JVM – přístup k zásobníkovým rámcům vláken sledované JVM přes rozhraní JDI
V dnešní části seriálu o programovacím jazyku Java i o virtuálním stroji Javy se již popáté budeme zabývat možnostmi nabízenými rozhraním JDI (Java Debugger Interface). Ukážeme si, jakým způsobem je možné toto rozhraní použít k získání obsahu zásobníkových rámců všech vláken aplikace běžící v cílové (sledované) JVM. Dnešní demonstrační příklad JDIStackTraceList sloužící k výpisu obsahu zásobníkových rámců jednotlivých vláken je založen na příkladu JDIVirtualMachineInfo, jehož zdrojový kód jsme si do všech podrobností popsali minule. Před popisem dnešního demonstračního příkladu i dalších možností rozhraní JDI si však nejprve připomeňme, jakým způsobem interně pracuje virtuální stroj Javy při spuštění a následném běhu javovské aplikace.
Při spuštění JVM i v průběhu inicializace javovské aplikace je pro každé vlákno vytvořen samostatný zásobník (stack) a pokud se v nějakém vláknu volá metoda, je pro toto volání na zásobníku vytvořen takzvaný zásobníkový rámec (stack frame), v němž je uložena jak informace o bodu návratu z metody (tedy místo, kam povede instrukce typu return v bajtkódu), tak i hodnoty všech parametrů předaných metodě i oblast s lokálními proměnnými metody. Označení „zásobník“ sice může navozovat pocit, že se jedná o kontinuální oblast paměti, ve skutečnosti však mezi jednotlivými zásobníkovými rámci neexistují žádné přímé vazby a proto se každý zásobníkový rámec může (ale nemusí) nacházet v paměti kdekoli – rámce například mohou být vytvářeny přímo na haldě (hodně zde záleží na konkrétní implementaci JVM). Podle specifikace JVM je totiž každý zásobník (stack) tvořen sekvencí vzájemně pouze nepřímo propojených zásobníkových rámců (stack frame(s)), což má i své dopady na větší bezpečnost javovských aplikací.
2. Získání a následný výpis obsahu zásobníkových rámců s využitím rozhraní JDI
Při použití rozhraní JVM TI bylo možné k zásobníkovým rámcům přistupovat například v callback funkcích zavolaných ve chvíli vzniku výjimky, popř. v callback funkcích zavolaných ve chvíli, kdy došlo ve sledované javovské aplikaci k zavolání nějaké metody. Tento přístup měl několik předností, ale i záporů. Pravděpodobně největší předností byl fakt, že při automatickém zavolání callback funkce zaregistrované v JVM TI agentovi došlo k pozastavení daného vlákna, což znamenalo, že bylo možné relativně bez problémů zjistit všechny zásobníkové rámce tohoto vlákna a provádět s nimi různé operace aniž by to ovlivnilo další vlákna aplikace. Nevýhodou bylo to, že přístup k zásobníkovým rámcům všech vláken již byl o poznání složitější (nehledě na složitost celého nízkoúrovňového JVM TI). Při použití rozhraní JDI se k problému přistupuje vlastně z opačné strany – máme totiž přístup ke všem vláknům aplikace a můžeme zjistit a zpracovat zásobníkové rámce libovolného vlákna, ovšem pouze v případě, že je toto vlákno pozastaveno (což je většinou nutné zařídit programově přímo z JDI).
Připomeňme si, že sledovaný (cílový) virtuální stroj Javy se spouští s následujícími parametry:
java -agentlib:jdwp=transport=dt_socket,server=y,address=6502,suspend=y Test2
Tyto parametry mj. zajistí i to, že ihned po spuštění virtuálního stroje a po inicializaci aplikace dojde k pozastavení všech vláken uvnitř JVM. Znamená to taktéž, že vlastně ani nedojde ke spuštění metody main v aplikaci, která má být v JVM spuštěna, takže výsledky, které z takto pozastaveného virtuálního stroje Javy získáme, budou poměrně nezajímavé. Ovšem díky rozhraní JDI není velkým problémem cílovou JVM na chvíli spustit, čímž již dojde ke spuštění metody main. Po přibližně jedné sekundě dojde k opětovnému pozastavení JVM. V našem demonstračním příkladu je spuštění a opětovné pozastavení cílové JVM zajištěno v uživatelské metodě nazvané runVirtualMachineForOneSecond, která vypadá následovně:
/** * Spustit sledovany virtualni stroj po dobu jedne sekundy * * @param virtualMachine sledovany virtualni stroj, k nemuz je debugger vzdalene pripojen */ private static void runVirtualMachineForOneSecond(VirtualMachine virtualMachine) { virtualMachine.resume(); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } virtualMachine.suspend(); }
Jakmile je cílová JVM opět pozastavena, je možné získat informace o všech vláknech a taktéž přečíst informace o všech zásobníkových rámcích jednotlivých vláken. V demonstračním příkladu je tato funkcionalita implementována v metodě printThreadInfo, kterou již částečně známe z předchozí části tohoto seriálu (zde pouze přibyl řádek s voláním metody printStackFrames):
/** * 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); printStackFrames(thread); } System.out.println(); }
Použité metody rozhraní JDI:
# | Třída/rozhraní | Návratový typ | Metoda |
---|---|---|---|
1 | com.sun.jdi.VirtualMachine | void | resume() |
2 | com.sun.jdi.VirtualMachine | void | suspend() |
3 | com.sun.jdi.ThreadReference | String | name() |
4 | com.sun.jdi.ObjectReference | long | uniqueID() |
5 | com.sun.jdi.ThreadReference | int | status() |
6 | com.sun.jdi.ThreadReference | boolean | isSuspended() |
3. Výpis informací o vybraném zásobníkovém rámci
Uživatelské metodě nazvané printStackFrames se předává objekt typu ThreadReference, který představuje vlákno v cílové JVM. Nejprve je voláním com.sun.jdi.ThreadReference.frameCount() získán počet zásobníkových rámců vytvořených pro vybrané vlákno a následně se projde všemi zásobníkovými rámci tohoto vlákna. Pro získání všech zásobníkových rámců se využívá metoda com.sun.jdi.ThreadReference.frames() vracející seznam objektů typu com.sun.jdi.StackFrame:
/** * Vypis zasobnikovych ramcu * * @param thread JDI objekt predstavujici vlakno */ private static void printStackFrames(ThreadReference thread) { try { System.out.format("%16s-------------------------------------------------\n", ""); System.out.format("%16sStack frame count: %d\n", "", thread.frameCount()); // vypsat informace o vsech zasobnikovych ramcich for (StackFrame frame : thread.frames()) { printStackFrameInfo(frame); } System.out.format("%16s-------------------------------------------------\n", ""); } catch (IncompatibleThreadStateException e) { e.printStackTrace(); } }
V uživatelské metodě printStackFrameInfo se již zjišťují a následně i vypisují informace o jednotlivých zásobníkových rámcích (o významu třídy Location si řekneme více informací v dalším textu):
/** * Vypis informaci o vybranem zasobnikovem ramci * * @param frame zasobnikovy ramec */ private static void printStackFrameInfo(StackFrame frame) { Location location = frame.location(); // ziskat vsechny informace o pozici v pozastavenem vlaknu String className = getClassName(location); String methodName = getMethodName(location); String sourceName = getSourceName(location); String lineNumber = getLineNumber(location); // nyni mame vsechny informace, lze je tedy vypsat System.out.format("%16s%s.%s (%s:%s)\n", "", className, methodName, sourceName, lineNumber); }
Význam metod getClassName, getMethodName, getSourceName a getLineNumber bude podrobněji vysvětlen ve čtvrté a v páté kapitole.
Použité metody rozhraní JDI:
# | Třída/rozhraní | Návratový typ | Metoda |
---|---|---|---|
1 | com.sun.jdi.ThreadReference | int | frameCount() |
2 | com.sun.jdi.ThreadReference | List<StackFrame> | frames() |
3 | com.sun.jdi.StackFrame | com.sun.jdi.Location | location() |
4. Přečtení jména volané metody i jména třídy, v níž je tato metoda deklarována
Každý vývojář používající programovací jazyk Java se již s velkou pravděpodobností setkal s takzvaným stack trace, což vlastně není nic jiného, než vhodným způsobem naformátovaný seznam volaných metod. V tomto seznamu se mj. vypisuje i jméno metody a taktéž jméno třídy, v níž byla tato metoda deklarována. Tyto dvě informace je možné s využitím rozhraní JDI získat velmi snadno. Nejdříve je nutné z objektu typu com.sun.jdi.StackFrame získat objekt typu com.sun.jdi.Location obsahující veškeré dostupné informace o volané metodě. Jakmile máme tento objekt k dispozici, lze snadno získat referenci volané metody a následně i jméno této metody, což je v demonstračním příkladu implementováno v getMethodName:
/** * Jmeno volane metody. */ private static String getMethodName(Location location) { return location.method().name(); }
Můžeme taktéž získat třídu, ve které je volaná metoda deklarována. Používá se zde volání com.sun.jdi.Method.declaringType() (přesněji řečeno com.sun.jdi.TypeComponent.declarintType()) vracející objekt typu ReferenceType:
/** * Jmeno tridy, jejiz metoda byla zavolana. */ private static String getClassName(Location location) { return location.method().declaringType().name(); }
Použité metody rozhraní JDI:
# | Třída/rozhraní | Návratový typ | Metoda |
---|---|---|---|
1 | com.sun.jdi.Location | com.sun.jdi.Method | method() |
2 | com.sun.jdi.Method | String | name() |
3 | com.sun.jdi.Method | com.sun.jdi.ReferenceType | declaringType() |
4 | com.sun.jdi.ReferenceType | String | name() |
5. Získání řádku, v níž došlo k volání metody a přečtení jména zdrojového souboru třídy
V každém stack trace se kromě jména třídy a jména volané metody objevují i další dvě informace. Jedná se o číslo řádku s voláním metody a taktéž o jméno zdrojového souboru obsahujícího deklaraci příslušné třídy či rozhraní – tato informace ovšem nemusí být dostupná vždy. Nejprve se podívejme, jak lze zjistit číslo řádku, v němž došlo k volání metody. Tuto informaci získáme pomocí com.sun.jdi.Location.lineNumber(), která vrátí kladné číslo v případě, že je číslo řádku známé a zápornou či nulovou hodnotu ve chvíli, kdy číslo řádku není možné z nějakého důvodu zjistit (typicky se jedná o nativní metody). Na tomto místě je ještě vhodné upozornit na to, že rozhraní JDI operuje s pojmem „strata“ (vrstvy), kde každé vrstvě („stratum“) může odpovídat jiný programovací jazyk v případě, že je zdrojový kód transformován z jednoho programovacího jazyka do jazyka jiného. Pokud je však testovací aplikace napsána přímo v Javě, nemusí nás problematika vrstev příliš trápit:
/** * Prevod cisla radku na retezec, pokud je to mozne. */ private static String getLineNumber(Location location) { int lineNumber = location.lineNumber(); // u nativnich metod nelze zjistit cisla radku return lineNumber >= 0 ? "" + lineNumber : "<native method>"; }
Zjištění jména zdrojového kódu, v němž je volaná metoda a samozřejmě i příslušná třída deklarována, je velmi jednoduché, protože pro tento účel lze použít volání com.sun.jdi.Location.sourceName(). V případě potřeby je možné zjistit i cestu k tomuto souboru s využitím com.sun.jdi.Location.sourcePath() (tuto informaci jsme v rozhraní JVM TI zjišťovali jen velmi složitým způsobem):
/** * Ziskani informaci o jmene zdrojoveho souboru pro danou lokaci. */ private static String getSourceName(Location location) { try { return location.sourceName(); } catch (AbsentInformationException e) { return "unknown"; } }
Použité metody rozhraní JDI:
# | Třída/rozhraní | Návratový typ | Metoda |
---|---|---|---|
1 | com.sun.jdi.Location | int | lineNumber() |
2 | com.sun.jdi.Location | String | sourceName() |
6. Kompletní zdrojový kód demonstračního příkladu JDIStackTraceList
V předchozích kapitolách byly popsány ty nejdůležitější metody, které jsou součástí dnešního demonstračního příkladu nazvaného JDIStackTraceList. Pod tímto odstavcem je vypsán celý zdrojový kód tohoto stále ještě 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.Location; import com.sun.jdi.StackFrame; 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 * * Po pripojeni se vypisou obsahy zasobnikovych ramcu vsech vlaken. * * @author Pavel Tisnovsky */ public class JDIStackTraceList { /** * 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); // spustit sledovany virtualni stroj po dobu jedne sekundy runVirtualMachineForOneSecond(virtualMachine); // 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; } /** * Spustit sledovany virtualni stroj po dobu jedne sekundy * * @param virtualMachine sledovany virtualni stroj, k nemuz je debugger vzdalene pripojen */ private static void runVirtualMachineForOneSecond(VirtualMachine virtualMachine) { virtualMachine.resume(); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } virtualMachine.suspend(); } /** * 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:"); printThreadInfo(virtualMachine); } /** * 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); printStackFrames(thread); } 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 zasobnikovych ramcu * * @param thread JDI objekt predstavujici vlakno */ private static void printStackFrames(ThreadReference thread) { try { System.out.format("%16s-------------------------------------------------\n", ""); System.out.format("%16sStack frame count: %d\n", "", thread.frameCount()); // vypsat informace o vsech zasobnikovych ramcich for (StackFrame frame : thread.frames()) { printStackFrameInfo(frame); } System.out.format("%16s-------------------------------------------------\n", ""); } catch (IncompatibleThreadStateException e) { e.printStackTrace(); } } /** * Vypis informaci o vybranem zasobnikovem ramci * * @param frame zasobnikovy ramec */ private static void printStackFrameInfo(StackFrame frame) { Location location = frame.location(); // ziskat vsechny informace o pozici v pozastavenem vlaknu String className = getClassName(location); String methodName = getMethodName(location); String sourceName = getSourceName(location); String lineNumber = getLineNumber(location); // nyni mame vsechny informace, lze je tedy vypsat System.out.format("%16s%s.%s (%s:%s)\n", "", className, methodName, sourceName, lineNumber); } /** * Jmeno tridy, jejiz metoda byla zavolana. */ private static String getClassName(Location location) { return location.method().declaringType().name(); } /** * Jmeno volane metody. */ private static String getMethodName(Location location) { return location.method().name(); } /** * Ziskani informaci o jmene zdrojoveho souboru pro danou lokaci. */ private static String getSourceName(Location location) { try { return location.sourceName(); } catch (AbsentInformationException e) { return "unknown"; } } /** * Prevod cisla radku na retezec, pokud je to mozne. */ private static String getLineNumber(Location location) { int lineNumber = location.lineNumber(); // u nativnich metod nelze zjistit cisla radku return lineNumber >= 0 ? "" + lineNumber : "<native method>"; } }
7. Spuštění demonstračního příkladu
Před vlastním spuštěním demonstračního příkladu JDIStackTraceList je nejprve nutné spustit samostatnou cílovou JVM i s testovanou aplikací. Tato aplikace je velmi jednoduchá, protože se po své inicializaci dostane do nekonečné smyčky, což nám samozřejmě vyhovuje, neboť bude jisté, že po cca jednosekundovém běhu cílové JVM (viz též druhou kapitolu) se bude hlavní vlákno aplikace skutečně nacházet uvnitř této smyčky. Povšimněte si, že nekonečné smyčky je dosaženo až po postupném volání main()→run()→foo()→bar():
public class Test2 { public void run() { foo(); } public void foo() { bar(); } public void bar() { while (true) { System.out.println("Hello world!"); } } public static void main(String[] args) { new Test2().run(); } }
Překlad a spuštění této aplikace v cílovém virtuálním stroji Javy se provede následujícím způsobem:
javac Test2.java java -agentlib:jdwp=transport=dt_socket,server=y,address=6502,suspend=y Test2
Nyní již zbývá provést překlad a spuštění našeho demonstračního příkladu:
javac -classpath /usr/lib/jvm/java-6-openjdk/lib/tools.jar:. JDIStackTraceList.java java -cp /usr/lib/jvm/java-6-openjdk/lib/tools.jar:. JDIStackTraceList
Po spuštění by se měl na standardním výstupu objevit přibližně tento text:
Connecting to virtual machine Connected Basic virtual machine info: Thread info: UniqueID Thread name Status Suspended 56 Attach Listener running yes ------------------------------------------------- Stack frame count: 0 ------------------------------------------------- 57 Signal Dispatcher running yes ------------------------------------------------- Stack frame count: 0 ------------------------------------------------- 58 Finalizer Object.wait yes ------------------------------------------------- Stack frame count: 4 java.lang.Object.wait (Object.java:<native method>) java.lang.ref.ReferenceQueue.remove (ReferenceQueue.java:116) java.lang.ref.ReferenceQueue.remove (ReferenceQueue.java:132) java.lang.ref.Finalizer$FinalizerThread.run (Finalizer.java:159) ------------------------------------------------- 59 Reference Handler Object.wait yes ------------------------------------------------- Stack frame count: 3 java.lang.Object.wait (Object.java:<native method>) java.lang.Object.wait (Object.java:485) java.lang.ref.Reference$ReferenceHandler.run (Reference.java:116) ------------------------------------------------- 1 main running yes ------------------------------------------------- Stack frame count: 15 java.io.FileOutputStream.writeBytes (FileOutputStream.java:<native method>) java.io.FileOutputStream.write (FileOutputStream.java:260) java.io.BufferedOutputStream.flushBuffer (BufferedOutputStream.java:65) java.io.BufferedOutputStream.flush (BufferedOutputStream.java:123) java.io.PrintStream.write (PrintStream.java:432) sun.nio.cs.StreamEncoder.writeBytes (StreamEncoder.java:202) sun.nio.cs.StreamEncoder.implFlushBuffer (StreamEncoder.java:272) sun.nio.cs.StreamEncoder.flushBuffer (StreamEncoder.java:85) java.io.OutputStreamWriter.flushBuffer (OutputStreamWriter.java:168) java.io.PrintStream.newLine (PrintStream.java:496) java.io.PrintStream.println (PrintStream.java:757) Test2.bar (Test2.java:13) Test2.foo (Test2.java:8) Test2.run (Test2.java:4) Test2.main (Test2.java:18) ------------------------------------------------- Calling exit
Obsah jednotlivých zásobníkových rámců se může nepatrně lišit, ovšem minimálně v případě vlákna pojmenovaného main by se měla na jeho začátku objevit sekvence volání:
Test2.bar (Test2.java:13) Test2.foo (Test2.java:8) Test2.run (Test2.java:4) Test2.main (Test2.java:18)
8. Zdrojové kódy demonstračního příkladu i podpůrných skriptů
Zdrojové kódy demonstračního příkladu JDIStackTraceList, testovací třídy i skriptů použitých pro překlad a spuštění tohoto demonstračního příkladu, byly uloženy (podobně jako tomu bylo i v předchozích částech tohoto seriálu) 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 | JDIStackTraceList | http://icedtea.classpath.org/people/ptisnovs/jvm-tools/file/8fad31d36206/jdi/JDIStackTraceList.java |
2 | compile_JDIStackTraceList.sh | http://icedtea.classpath.org/people/ptisnovs/jvm-tools/file/8fad31d36206/jdi/compile_JDIStackTraceList.sh |
3 | Test2.java | http://icedtea.classpath.org/people/ptisnovs/jvm-tools/file/8fad31d36206/jdi/Test2.java |
4 | Test2.sh | http://icedtea.classpath.org/people/ptisnovs/jvm-tools/file/8fad31d36206/jdi/Test2.sh |
9. 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