Obsah
1. Pohled pod kapotu JVM – dokončení popisu rozhraní JDI: krokování
2. Povolení a nastavení krokování v metodě registerStepEvent()
3. Získání reference hlavního vlákna aplikace běžící v monitorovaném virtuálním stroji
4. Smyčka pro zpracování událostí
5. Výpis všech relevantních informací při každém kroku
6. Úplný zdrojový kód demonstračního příkladu JDIStepRequestDemo
7. Zdrojový kód testovací třídy Test8
8. Informace vypisované demonstračním příkladem po jeho spuštění: varianta se STEP_LINE
9. Informace vypisované demonstračním příkladem po jeho spuštění: varianta se STEP_MIN
10. Repositář se zdrojovými kódy demonstračních příkladů
1. Pohled pod kapotu JVM – dokončení popisu rozhraní JDI: krokování
V předchozích dvanácti dílech seriálu o programovacím jazyku Java i o virtuálním stroji Javy jsme si popsali většinu funkcí nabízených rozhraním JDI (Java Debugger Interface). Dnes se popisem tohoto rozhraní budeme zabývat naposledy, protože další část tohoto seriálu bude věnována nástrojům určeným pro vytváření či modifikaci bajtkódu JVM. Minule jsme si na dvojici demonstračních příkladů ukázali způsob využití breakpointů, které je možné nastavit na konkrétní programové řádky. Ovšem naprostá většina debuggerů si nevystačí s běžnými breakpointy – debuggery totiž svým uživatelům nabízí i možnost aplikací krokovat, a to buď po jednotlivých programových řádcích, či po jednotlivých příkazech/výrazech. Možnost krokování programů je většinou spojena s nastavením breakpointů, ovšem to je pouze uživatelský pohled na celou problematiku – při implementaci debuggeru lze práci s breakpointy a s krokováním programů od sebe oddělit.
K oddělení práce s breakpointy a s krokováním programů došlo i při návrhu rozhraní JDI. Připomeňme si, že breakpoint se registruje s využitím metody:
EventRequestManager.createBreakpointRequest(com.sun.jdi.Location)
To mj. znamená, že breakpoint je zaregistrován na určitou lokaci v kódu sledované aplikace a současně se jedná o breakpoint platný pro všechna vlákna. Naproti tomu se podpora pro krokování nastavuje pro jedno vybrané vlákno a přitom se neuvádí žádná lokace:
EventRequestManager.createStepRequest(ThreadReference thread, int size, int depth)
Význam argumentů size a depth si vysvětlíme v navazující kapitole.
Pokud je krokování povoleno, je při každém kroku vytvořena událost typu StepEvent, která je zapsána do fronty událostí (event queue), stejně jako je tomu i u všech dalších typů událostí.
2. Povolení a nastavení krokování v metodě registerStepEvent()
Způsob krokování sledovanou (monitorovanou) aplikací si, podobně jako tomu bylo i v několika předchozích dílech tohoto seriálu, vysvětlíme na demonstračním debuggeru, který se bude jmenovat JDIStepRequestDemo. Tento debugger se bude ke sledovanému virtuálnímu stroji připojovat přes socket (port je stále nastaven na 6502) a po připojení se v uživatelské metodě acquireAndUseEventRequestManager() provede registrace dvou typů událostí – nám již známé události VMDeathEvent (na kterou musí debugger reagovat odpojením od sledované JVM) a taktéž události typu StepEvent. Zdrojový kód uživatelské metody acquireAndUseEventRequestManager() se tedy v mnoha ohledech podobá stejnojmenné metodě použité i v předchozích demonstračních příkladech:
/** * Ukazka pouziti EventRequestManageru. * * @param virtualMachine * sledovany virtualni stroj, k nemuz je debugger vzdalene * pripojen */ private static void acquireAndUseEventRequestManager(VirtualMachine virtualMachine) { EventRequestManager eventRequestManager = virtualMachine.eventRequestManager(); // tuto udalost potrebujeme zpracovavat pro korektni ukonceni debuggeru VMDeathRequest vmDeathRequest = registerVMDeathEvent(eventRequestManager); // zadost o generovani udalosti vytvorenych pri pristupu na breakpoint StepRequest stepRequest = registerStepEvent(virtualMachine, eventRequestManager); printInfoAboutStepEvent(stepRequest); // klasicka smycka pro zpracovani udalosti eventLoop(virtualMachine, vmDeathRequest); }
Vlastní registrace události typu StepEvent se provádí v další uživatelské metodě pojmenované registerStepEvent(). Tato metoda tvoří v určitém ohledu nejdůležitější část celého demonstračního debuggeru, takže si ji vysvětleme podrobněji. Při registraci podpory pro krokování se kromě reference na konkrétní vlákno ještě zadávají dva argumenty. První celočíselný argument může nabývat hodnoty StepRequest.STEP_LINE v případě, že se má krokování provádět po jednotlivých programových řádcích, popř. StepRequest.STEP_MIN pokud vyžadujeme krokování po co nejmenších celcích (například části výrazu – zde již záleží na vlastnostech konkrétní JVM i JITu, jak přesné krokování bude). Ve druhém celočíselném argumentu lze předat konstantu StepRequest.STEP_INTO, StepRequest.STEP_OVER či StepRequest.STEP_OUT v závislosti na tom, zda se má přeskočit volání metody jako jeden krok, zda se má provést krokování i volaných metod či zda se naopak má krokovat jen volající metoda. Navíc je možné omezit krokování pouze na určitou třídu přes metodu StepEvent.addClassFilter():
/** * Registrace udalosti typu StepEvent. * * @param virtualMachine * sledovany virtualni stroj, k nemuz je debugger vzdalene * pripojen * @param eventRequestManager * objekt zajistujici praci s registraci udalosti * @return objekt typu StepRequest */ private static StepRequest registerStepEvent(VirtualMachine virtualMachine, EventRequestManager eventRequestManager) { // ziskat hlavni vlakno aplikace bezici ve sledovanem virtualnim stroji ThreadReference mainThread = getMainThread(virtualMachine); if (mainThread == null) { return null; } StepRequest stepRequest = eventRequestManager.createStepRequest(mainThread, StepRequest.STEP_MIN, StepRequest.STEP_INTO); // krokovane vlakno se ma zastavit po kazdem kroku stepRequest.setSuspendPolicy(EventRequest.SUSPEND_EVENT_THREAD); // chceme krokovat pouze testovaci tridou stepRequest.addClassFilter(TEST_CLASS_NAME); stepRequest.enable(); return stepRequest; }
3. Získání reference hlavního vlákna aplikace běžící v monitorovaném virtuálním stroji
V uživatelské metodě registerStepEvent() popsané v předchozí kapitole se při volání EventRequestManager.createStepRequest() musela v prvním argumentu předat reference na vlákno, pro nějž se krokování povoluje (reference na vlákno je představována instancí třídy ThreadReference). V našem případě budeme chtít krokovat „hlavní“ vlákno celé aplikace, které nese název „main“. Jeden z nejjednodušších způsobů, jakým je možné referenci na toto vlákno získat, je implementován v uživatelské metodě nazvané příhodně getMainThread(). V této metodě se nejdříve přes volání:
List<ThreadReference> VirtualMachine.allThreads()
získají všechna vlákna existující v monitorovaném virtuálním stroji Javy a posléze se najde to vlákno, které má nastaveno jméno na „main“. Pokud by takové vlákno neexistovalo (což je u běžných typů aplikací nepravděpodobné), vrátí se namísto reference null:
/** * Ziskani hlavniho vlakna aplikace bezici v monitorovanem virtualnim stroji. * * @param virtualMachine * sledovany virtualni stroj, k nemuz je debugger vzdalene * pripojen * @return */ private static ThreadReference getMainThread(VirtualMachine virtualMachine) { List<ThreadReference> threads = virtualMachine.allThreads(); // projit vsemi vlakny bezicimi ve sledovanem virtualnim stroji for (ThreadReference thread : threads) { // najit hlavni vlakno if (MAIN_THREAD_NAME.equals(thread.name())) { return thread; } } // hlavni vlakno nebylo nalezeno, podivne return null; }
4. Smyčka pro zpracování událostí
Smyčku událostí implementovanou v uživatelské metodě eventLoop() si nemusíme podrobněji popisovat, neboť ji již známe z mnoha předchozích demonstračních příkladů:
/** * Klasicka smycka pro postupne zpracovani udalosti. * * @param virtualMachine * sledovany virtualni stroj, k nemuz je debugger vzdalene * pripojen * @param vmDeathRequest * objekt pro rizeni udalosti typu VMDeathEvent */ private static void eventLoop(VirtualMachine virtualMachine, VMDeathRequest vmDeathRequest) { EventQueue eventQueue = virtualMachine.eventQueue(); // precist a zpracovat udalosti while (processEvents(eventQueue, vmDeathRequest)) { // jojo tady skutecne nic neni :) } }
Ke změně však došlo v další uživatelské metodě nazvané processEvents(), v níž se jednotlivé události postupně čtou z fronty událostí (event queue) a následně se zpracovávají. V dnešním demonstračním příkladu budeme reagovat na událost typu VMDeathEvent (to již známe) a taktéž na událost typu StepEvent. Za povšimnutí stojí i příkaz eventSet.resume(), který znovuspustí vlákno/vlákna sledované aplikace:
/** * Precteni a zpracovani udalosti. * * @param eventQueue * fronta udalosti * @param vmDeathRequest * objekt pro rizeni udalosti typu VMDeathEvent * * @return true pokud se ma pokracovat ve zpracovavani udalosti false pokud * se ma sledovana VM ukoncit */ private static boolean processEvents(EventQueue eventQueue, VMDeathRequest vmDeathRequest) { EventSet eventSet; try { // precist udalosti z fronty eventSet = eventQueue.remove(); int events = eventSet.size(); System.out.println("Got " + events + " request" + (events > 1 ? "s:" : ":")); // projit vsemi udalostmi for (Event event : eventSet) { if (event instanceof VMStartEvent) { System.out.println(" VMStartEvent"); } else if (event instanceof VMDeathEvent) { System.out.println(" VMDeathEvent"); // zakazat dalsi generovani udalosti // (u VMDeathEvent je ve skutecnosti vzdy posledni udalost poslana) vmDeathRequest.disable(); // posleze se zavola shutdownVirtualMachine() return false; } else if (event instanceof StepEvent) { // nejake vlakno se zastavilo pro "krokovani" printStepInfo((StepEvent)event); } else { System.out.println(" other event"); } } // znovu postit vsechna vlakna eventSet.resume(); } catch (InterruptedException e) { e.printStackTrace(); } return true; }
5. Výpis všech relevantních informací při každém kroku
Ve smyčce událostí je při přečtení události typu StepEvent zavolána uživatelská metoda printStepInfo(), v níž dojde k výpisu základních informací o programovém řádku, v němž došlo k dalšímu kroku. Především se zjistí vlastní číslo programového řádku (pokud je k dispozici – je nutné překládat s parametrem -g), dále pak jméno třídy i jméno příslušné metody společně s informacemi o jménech a hodnotách lokálních proměnných i argumentů metody. Některé z těchto informací se zjišťují v dalších uživatelských metodách, které si však dnes již nebudeme popisovat, neboť jsme se s nimi seznámili v předchozích částech tohoto seriálu:
/** * Vypis informaci o kazdem kroku. * * @param event udalost */ private static void printStepInfo(StepEvent event) { // ziskat objekt typu Location, ktery obsahuje informaci o tom, // ve kterem miste kodu se provedl krok Location location = event.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 jiz mame vsechny dulezite informace, // lze je tedy vypsat System.out.format(" at: %s.%s ", className, methodName); // po vypisu zakladnich informaci se jeste vypise jmeno zdrojoveho // souboru a cislo radku metody System.out.format("(%s:%s)\n", sourceName, lineNumber); // ziskat referenci na vlakno ThreadReference threadRef = event.thread(); try { // ziskat posledni zasobnikovy ramec vlakna StackFrame stackFrame = threadRef.frame(0); // a projit vsemi viditelnymi promennymi List<LocalVariable> visibleVars = stackFrame.visibleVariables(); for (LocalVariable visibleVar : visibleVars) { Value val = stackFrame.getValue(visibleVar); System.out.println(" Local variable: " + visibleVar.name() + " = " + val); } } catch (IncompatibleThreadStateException e) { e.printStackTrace(); } catch (AbsentInformationException e) { e.printStackTrace(); } }
6. Úplný zdrojový kód demonstračního příkladu JDIStepRequestDemo
Nyní již máme dostatek informací k tomu, abychom si mohli ukázat celý zdrojový kód dnešního demonstračního debuggeru nazvaného JDIStepRequestDemo. Ten pracuje následovně: nejdříve se zaregistrují oba výše zmíněné typy zpracovávaných událostí (VMDeathEvent, StepEvent) a posléze se spustí smyčka událostí (event loop). Jakmile hlavní vlákno (vlákno se jménem „main“) dojde na další programový řádek popř. na další dále nedělitelnou část příkazu, vypíše se na standardní výstup informace o řádku (ve zdrojovém kódu), jménu třídy i jménu metody. Pod těmito základními informacemi se vypíšou i hodnoty parametrů metody a hodnoty jejích lokálních proměnných. Posléze se hlavní vlákno opět spustí:
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.LocalVariable; import com.sun.jdi.Location; import com.sun.jdi.StackFrame; import com.sun.jdi.ThreadReference; import com.sun.jdi.Value; 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; import com.sun.jdi.event.Event; import com.sun.jdi.event.EventQueue; import com.sun.jdi.event.EventSet; import com.sun.jdi.event.StepEvent; import com.sun.jdi.event.VMDeathEvent; import com.sun.jdi.event.VMStartEvent; import com.sun.jdi.request.EventRequest; import com.sun.jdi.request.EventRequestManager; import com.sun.jdi.request.StepRequest; import com.sun.jdi.request.VMDeathRequest; /** * 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 zaregistruje udalost typu 'Step' a pri behu aplikace * se budou postupne vypisovat trasovaci informace pro hlavni vlakno. * * @author Pavel Tisnovsky */ public class JDIStepRequestDemo { /** * 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"; /** * Jmeno testovane tridy. */ private static final String TEST_CLASS_NAME = "Test8"; /** * Jmeno hlavniho vlakna. */ private static final String MAIN_THREAD_NAME = "main"; /** * Vstupni metoda tohoto demonstracniho debuggeru. */ 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; } // jsme pripojeni ke sledovane JVM, takze lze provadet ladeni debugVirtualMachineUsingSocket(connector); } /** * Ziskat konektor pouzivajici pro pripojeni sockety * * @param connectors * seznam vsech dostupnych konektoru pro pripojeni * @return 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; } } // nenasli jsme zadny vhodny konektor return null; } /** * Pripojeni k bezicimu virtualnimu stroji pres socket. * * @param connector * konektor pouzivajici pro pripojeni sockety * @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 Javy runVirtualMachine(virtualMachine); // ukazka pouziti EventRequestManageru acquireAndUseEventRequestManager(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. * * @param virtualMachine * sledovany virtualni stroj, k nemuz je debugger vzdalene * pripojen */ private static void runVirtualMachine(VirtualMachine virtualMachine) { virtualMachine.resume(); } /** * 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); } /** * Ukazka pouziti EventRequestManageru. * * @param virtualMachine * sledovany virtualni stroj, k nemuz je debugger vzdalene * pripojen */ private static void acquireAndUseEventRequestManager(VirtualMachine virtualMachine) { EventRequestManager eventRequestManager = virtualMachine.eventRequestManager(); // tuto udalost potrebujeme zpracovavat pro korektni ukonceni debuggeru VMDeathRequest vmDeathRequest = registerVMDeathEvent(eventRequestManager); // zadost o generovani udalosti vytvorenych pri pristupu na breakpoint StepRequest stepRequest = registerStepEvent(virtualMachine, eventRequestManager); printInfoAboutStepEvent(stepRequest); // klasicka smycka pro zpracovani udalosti eventLoop(virtualMachine, vmDeathRequest); } /** * Vypis informace o registraci udalosti typu 'Step' * * @param stepRequest * instance tridy implementujici rozhrani StepRequest */ private static void printInfoAboutStepEvent(StepRequest stepRequest) { if (stepRequest != null) { System.out.println("'Step event' has been registered: " + stepRequest.toString()); } else { System.out.println("Unable to register 'Step event'"); } } /** * Registrace udalosti typu VMDeathEvent. * * @param eventRequestManager * objekt zajistujici praci s registraci udalosti * @return objekt typu VMDeathRequest */ private static VMDeathRequest registerVMDeathEvent(EventRequestManager eventRequestManager) { VMDeathRequest vmDeathRequest = eventRequestManager.createVMDeathRequest(); // po registraci udalosti je jeste nutne tento typ udalosti povolit vmDeathRequest.enable(); return vmDeathRequest; } /** * Registrace udalosti typu StepEvent. * * @param virtualMachine * sledovany virtualni stroj, k nemuz je debugger vzdalene * pripojen * @param eventRequestManager * objekt zajistujici praci s registraci udalosti * @return objekt typu StepRequest */ private static StepRequest registerStepEvent(VirtualMachine virtualMachine, EventRequestManager eventRequestManager) { // ziskat hlavni vlakno aplikace bezici ve sledovanem virtualnim stroji ThreadReference mainThread = getMainThread(virtualMachine); if (mainThread == null) { return null; } StepRequest stepRequest = eventRequestManager.createStepRequest(mainThread, StepRequest.STEP_MIN, StepRequest.STEP_INTO); // krokovane vlakno se ma zastavit po kazdem kroku stepRequest.setSuspendPolicy(EventRequest.SUSPEND_EVENT_THREAD); // chceme krokovat pouze testovaci tridou stepRequest.addClassFilter(TEST_CLASS_NAME); stepRequest.enable(); return stepRequest; } /** * Ziskani hlavniho vlakna aplikace bezici v monitorovanem virtualnim stroji. * * @param virtualMachine * sledovany virtualni stroj, k nemuz je debugger vzdalene * pripojen * @return */ private static ThreadReference getMainThread(VirtualMachine virtualMachine) { List<ThreadReference> threads = virtualMachine.allThreads(); // projit vsemi vlakny bezicimi ve sledovanem virtualnim stroji for (ThreadReference thread : threads) { // najit hlavni vlakno if (MAIN_THREAD_NAME.equals(thread.name())) { return thread; } } // hlavni vlakno nebylo nalezeno, podivne return null; } /** * Klasicka smycka pro postupne zpracovani udalosti. * * @param virtualMachine * sledovany virtualni stroj, k nemuz je debugger vzdalene * pripojen * @param vmDeathRequest * objekt pro rizeni udalosti typu VMDeathEvent */ private static void eventLoop(VirtualMachine virtualMachine, VMDeathRequest vmDeathRequest) { EventQueue eventQueue = virtualMachine.eventQueue(); // precist a zpracovat udalosti while (processEvents(eventQueue, vmDeathRequest)) { // jojo tady skutecne nic neni :) } } /** * Precteni a zpracovani udalosti. * * @param eventQueue * fronta udalosti * @param vmDeathRequest * objekt pro rizeni udalosti typu VMDeathEvent * * @return true pokud se ma pokracovat ve zpracovavani udalosti false pokud * se ma sledovana VM ukoncit */ private static boolean processEvents(EventQueue eventQueue, VMDeathRequest vmDeathRequest) { EventSet eventSet; try { // precist udalosti z fronty eventSet = eventQueue.remove(); int events = eventSet.size(); System.out.println("Got " + events + " request" + (events > 1 ? "s:" : ":")); // projit vsemi udalostmi for (Event event : eventSet) { if (event instanceof VMStartEvent) { System.out.println(" VMStartEvent"); } else if (event instanceof VMDeathEvent) { System.out.println(" VMDeathEvent"); // zakazat dalsi generovani udalosti // (u VMDeathEvent je ve skutecnosti vzdy posledni udalost poslana) vmDeathRequest.disable(); // posleze se zavola shutdownVirtualMachine() return false; } else if (event instanceof StepEvent) { // nejake vlakno se zastavilo pro "krokovani" printStepInfo((StepEvent)event); } else { System.out.println(" other event"); } } // znovu postit vsechna vlakna eventSet.resume(); } catch (InterruptedException e) { e.printStackTrace(); } return true; } /** * Vypis informaci o kazdem kroku. * * @param event udalost */ private static void printStepInfo(StepEvent event) { // ziskat objekt typu Location, ktery obsahuje informaci o tom, // ve kterem miste kodu se provedl krok Location location = event.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 jiz mame vsechny dulezite informace, // lze je tedy vypsat System.out.format(" at: %s.%s ", className, methodName); // po vypisu zakladnich informaci se jeste vypise jmeno zdrojoveho // souboru a cislo radku metody System.out.format("(%s:%s)\n", sourceName, lineNumber); // ziskat referenci na vlakno ThreadReference threadRef = event.thread(); try { // ziskat posledni zasobnikovy ramec vlakna StackFrame stackFrame = threadRef.frame(0); // a projit vsemi viditelnymi promennymi List<LocalVariable> visibleVars = stackFrame.visibleVariables(); for (LocalVariable visibleVar : visibleVars) { Value val = stackFrame.getValue(visibleVar); System.out.println(" Local variable: " + visibleVar.name() + " = " + val); } } catch (IncompatibleThreadStateException e) { e.printStackTrace(); } catch (AbsentInformationException e) { e.printStackTrace(); } } /** * 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. Zdrojový kód testovací třídy Test8
Dnešní demonstrační debugger JDIStepRequestDemo budeme testovat proti sledovanému virtuálnímu stroji Javy, v němž bude spuštěna velmi jednoduchá aplikace představovaná třídou Test8. Tuto třídu jsme ostatně využili i minule, takže si již bez dalšího popisu uvedeme její zdrojový kód. Za zmínku stojí především tělo metody run(), neboť se v něm nachází programová smyčka měnící dvojici lokálních proměnných – počitadla smyčky i a druhé lokální proměnné y:
public class Test8 { public void run(int x) { int y = 0; for (int i = 1; i < 10; i++) { y += x; } } public static void main(String[] args) { System.out.println("Press any key"); try { System.in.read(); } catch (java.io.IOException e) { // . // } new Test8().run(10); } }
8. Informace vypisované demonstračním příkladem po jeho spuštění: varianta se STEP_LINE
Ve druhé kapitole jsme si řekli, že při požadavku na krokování určitého vlákna je možné při konstrukci objektu typu StepRequest určit, zda se má krokování provádět po jednotlivých programových řádcích či naopak po (obecně) menších krocích představujících z hlediska běhu vlákna již dále nedělitelnou entitu. V případě, že je testovací třída Test8 přeložena s použitím parametru -g a v demonstračním debuggeru použijeme v metodě registerStepEvent() konstantu StepRequest.STEP_LINE, měl by debugger při svém spuštění vypsat následující zprávy, z nichž lze například vyčíst chování programové smyčky na řádcích Test8.java:5 a Test8.java:6:
Connecting to virtual machine Connected 'Step event' has been registered: step request instance of java.lang.Thread(name='main', id=1) (enabled) Got 1 request: at: Test8.main (Test8.java:13) Local variable: args = instance of java.lang.String[0] (id=8) Got 1 request: at: Test8.main (Test8.java:17) Local variable: args = instance of java.lang.String[0] (id=8) Got 1 request: at: Test8.main (Test8.java:18) Local variable: args = instance of java.lang.String[0] (id=8) Got 1 request: at: Test8.<init> (Test8.java:1) Got 1 request: at: Test8.main (Test8.java:18) Local variable: args = instance of java.lang.String[0] (id=8) Got 1 request: at: Test8.run (Test8.java:4) Local variable: x = 10 Got 1 request: at: Test8.run (Test8.java:5) Local variable: x = 10 Local variable: y = 0 Got 1 request: at: Test8.run (Test8.java:6) Local variable: x = 10 Local variable: y = 0 Local variable: i = 1 Got 1 request: at: Test8.run (Test8.java:5) Local variable: x = 10 Local variable: y = 10 Local variable: i = 1 Got 1 request: at: Test8.run (Test8.java:6) Local variable: x = 10 Local variable: y = 10 Local variable: i = 2 Got 1 request: at: Test8.run (Test8.java:5) Local variable: x = 10 Local variable: y = 20 Local variable: i = 2 Got 1 request: at: Test8.run (Test8.java:6) Local variable: x = 10 Local variable: y = 20 Local variable: i = 3 Got 1 request: at: Test8.run (Test8.java:5) Local variable: x = 10 Local variable: y = 30 Local variable: i = 3 Got 1 request: at: Test8.run (Test8.java:6) Local variable: x = 10 Local variable: y = 30 Local variable: i = 4 Got 1 request: at: Test8.run (Test8.java:5) Local variable: x = 10 Local variable: y = 40 Local variable: i = 4 Got 1 request: at: Test8.run (Test8.java:6) Local variable: x = 10 Local variable: y = 40 Local variable: i = 5 Got 1 request: at: Test8.run (Test8.java:5) Local variable: x = 10 Local variable: y = 50 Local variable: i = 5 Got 1 request: at: Test8.run (Test8.java:6) Local variable: x = 10 Local variable: y = 50 Local variable: i = 6 Got 1 request: at: Test8.run (Test8.java:5) Local variable: x = 10 Local variable: y = 60 Local variable: i = 6 Got 1 request: at: Test8.run (Test8.java:6) Local variable: x = 10 Local variable: y = 60 Local variable: i = 7 Got 1 request: at: Test8.run (Test8.java:5) Local variable: x = 10 Local variable: y = 70 Local variable: i = 7 Got 1 request: at: Test8.run (Test8.java:6) Local variable: x = 10 Local variable: y = 70 Local variable: i = 8 Got 1 request: at: Test8.run (Test8.java:5) Local variable: x = 10 Local variable: y = 80 Local variable: i = 8 Got 1 request: at: Test8.run (Test8.java:6) Local variable: x = 10 Local variable: y = 80 Local variable: i = 9 Got 1 request: at: Test8.run (Test8.java:5) Local variable: x = 10 Local variable: y = 90 Local variable: i = 9 Got 1 request: at: Test8.run (Test8.java:8) Local variable: x = 10 Local variable: y = 90 Got 1 request: at: Test8.main (Test8.java:19) Local variable: args = instance of java.lang.String[0] (id=8) Got 2 requests: VMDeathEvent Calling exit
9. Informace vypisované demonstračním příkladem po jeho spuštění: varianta se STEP_MIN
V této kapitole si ukážeme, jak by mohly vypadat zprávy vypisované demonstračním debuggerem JDIStepRequestDemo tehdy, pokud je v uživatelské metodě createStepRequest() použita při konstrukci objektu typu StepRequest konstanta StepRequest.STEP_MIN. V tomto případě je totiž krokování hlavního vlákna prováděno po menších celcích, než jsou jednotlivé programové řádky. O jak velké (či možná přesněji řečeno malé) celky se jedná, je závislé na implementaci vlastního virtuálního stroje, popř. na parametrech JITu. Nicméně na testovací JVM je možné z následujícího výpisu vyčíst například zajímavé informace o již dvakrát zmiňované programové smyčce:
for (int i = 1; i < 10; i++) { y += x; }
První řádek programové smyčky je v každé iteraci rozdělen do pěti kroků, zatímco řádek druhý (tělo smyčky) je rozdělen do kroků čtyř. Částečně to souvisí s instrukcemi bajtkódu implementujícího tuto smyčku:
4: iload_3 // radek 1 - ridici blok smycky 5: bipush 10 // radek 1 - ridici blok smycky 7: if_icmpge 20 // radek 1 - ridici blok smycky 10: iload_2 // radek 2 - telo smycky 11: iload_1 // radek 2 - telo smycky 12: iadd // radek 2 - telo smycky 13: istore_2 // radek 2 - telo smycky 14: iinc 3, 1 // radek 1 - ridici blok smycky 17: goto 4 // radek 1 - ridici blok smycky
Nyní již následuje slíbený výpis zpráv generovaný demonstračním příkladem:
Connecting to virtual machine Connected 'Step event' has been registered: step request instance of java.lang.Thread(name='main', id=1) (enabled) Got 1 request: at: Test8.main (Test8.java:13) Local variable: args = instance of java.lang.String[0] (id=8) Got 1 request: at: Test8.main (Test8.java:17) Local variable: args = instance of java.lang.String[0] (id=8) Got 1 request: at: Test8.main (Test8.java:18) Local variable: args = instance of java.lang.String[0] (id=8) Got 1 request: at: Test8.main (Test8.java:18) Local variable: args = instance of java.lang.String[0] (id=8) Got 1 request: at: Test8.main (Test8.java:18) Local variable: args = instance of java.lang.String[0] (id=8) Got 1 request: at: Test8.<init> (Test8.java:1) Got 1 request: at: Test8.<init> (Test8.java:1) Got 1 request: at: Test8.<init> (Test8.java:1) Got 1 request: at: Test8.main (Test8.java:18) Local variable: args = instance of java.lang.String[0] (id=8) Got 1 request: at: Test8.main (Test8.java:18) Local variable: args = instance of java.lang.String[0] (id=8) Got 1 request: at: Test8.run (Test8.java:4) Local variable: x = 10 Got 1 request: at: Test8.run (Test8.java:4) Local variable: x = 10 Got 1 request: at: Test8.run (Test8.java:5) Local variable: x = 10 Local variable: y = 0 Got 1 request: at: Test8.run (Test8.java:5) Local variable: x = 10 Local variable: y = 0 Got 1 request: at: Test8.run (Test8.java:5) Local variable: x = 10 Local variable: y = 0 Local variable: i = 1 Got 1 request: at: Test8.run (Test8.java:5) Local variable: x = 10 Local variable: y = 0 Local variable: i = 1 Got 1 request: at: Test8.run (Test8.java:5) Local variable: x = 10 Local variable: y = 0 Local variable: i = 1 Got 1 request: at: Test8.run (Test8.java:6) Local variable: x = 10 Local variable: y = 0 Local variable: i = 1 Got 1 request: at: Test8.run (Test8.java:6) Local variable: x = 10 Local variable: y = 0 Local variable: i = 1 Got 1 request: at: Test8.run (Test8.java:6) Local variable: x = 10 Local variable: y = 0 Local variable: i = 1 Got 1 request: at: Test8.run (Test8.java:6) Local variable: x = 10 Local variable: y = 0 Local variable: i = 1 Got 1 request: at: Test8.run (Test8.java:5) Local variable: x = 10 Local variable: y = 10 Local variable: i = 1 Got 1 request: at: Test8.run (Test8.java:5) Local variable: x = 10 Local variable: y = 10 Local variable: i = 2 Got 1 request: at: Test8.run (Test8.java:5) Local variable: x = 10 Local variable: y = 10 Local variable: i = 2 Got 1 request: at: Test8.run (Test8.java:5) Local variable: x = 10 Local variable: y = 10 Local variable: i = 2 Got 1 request: at: Test8.run (Test8.java:5) Local variable: x = 10 Local variable: y = 10 Local variable: i = 2 Got 1 request: at: Test8.run (Test8.java:6) Local variable: x = 10 Local variable: y = 10 Local variable: i = 2 Got 1 request: at: Test8.run (Test8.java:6) Local variable: x = 10 Local variable: y = 10 Local variable: i = 2 Got 1 request: at: Test8.run (Test8.java:6) Local variable: x = 10 Local variable: y = 10 Local variable: i = 2 Got 1 request: at: Test8.run (Test8.java:6) Local variable: x = 10 Local variable: y = 10 Local variable: i = 2 Got 1 request: at: Test8.run (Test8.java:5) Local variable: x = 10 Local variable: y = 20 Local variable: i = 2 Got 1 request: at: Test8.run (Test8.java:5) Local variable: x = 10 Local variable: y = 20 Local variable: i = 3 Got 1 request: at: Test8.run (Test8.java:5) Local variable: x = 10 Local variable: y = 20 Local variable: i = 3 Got 1 request: at: Test8.run (Test8.java:5) Local variable: x = 10 Local variable: y = 20 Local variable: i = 3 Got 1 request: at: Test8.run (Test8.java:5) Local variable: x = 10 Local variable: y = 20 Local variable: i = 3 Got 1 request: at: Test8.run (Test8.java:6) Local variable: x = 10 Local variable: y = 20 Local variable: i = 3 Got 1 request: at: Test8.run (Test8.java:6) Local variable: x = 10 Local variable: y = 20 Local variable: i = 3 Got 1 request: at: Test8.run (Test8.java:6) Local variable: x = 10 Local variable: y = 20 Local variable: i = 3 Got 1 request: at: Test8.run (Test8.java:6) Local variable: x = 10 Local variable: y = 20 Local variable: i = 3 Got 1 request: at: Test8.run (Test8.java:5) Local variable: x = 10 Local variable: y = 30 Local variable: i = 3 Got 1 request: at: Test8.run (Test8.java:5) Local variable: x = 10 Local variable: y = 30 Local variable: i = 4 Got 1 request: at: Test8.run (Test8.java:5) Local variable: x = 10 Local variable: y = 30 Local variable: i = 4 Got 1 request: at: Test8.run (Test8.java:5) Local variable: x = 10 Local variable: y = 30 Local variable: i = 4 Got 1 request: at: Test8.run (Test8.java:5) Local variable: x = 10 Local variable: y = 30 Local variable: i = 4 Got 1 request: at: Test8.run (Test8.java:6) Local variable: x = 10 Local variable: y = 30 Local variable: i = 4 Got 1 request: at: Test8.run (Test8.java:6) Local variable: x = 10 Local variable: y = 30 Local variable: i = 4 Got 1 request: at: Test8.run (Test8.java:6) Local variable: x = 10 Local variable: y = 30 Local variable: i = 4 Got 1 request: at: Test8.run (Test8.java:6) Local variable: x = 10 Local variable: y = 30 Local variable: i = 4 Got 1 request: at: Test8.run (Test8.java:5) Local variable: x = 10 Local variable: y = 40 Local variable: i = 4 Got 1 request: at: Test8.run (Test8.java:5) Local variable: x = 10 Local variable: y = 40 Local variable: i = 5 Got 1 request: at: Test8.run (Test8.java:5) Local variable: x = 10 Local variable: y = 40 Local variable: i = 5 Got 1 request: at: Test8.run (Test8.java:5) Local variable: x = 10 Local variable: y = 40 Local variable: i = 5 Got 1 request: at: Test8.run (Test8.java:5) Local variable: x = 10 Local variable: y = 40 Local variable: i = 5 Got 1 request: at: Test8.run (Test8.java:6) Local variable: x = 10 Local variable: y = 40 Local variable: i = 5 Got 1 request: at: Test8.run (Test8.java:6) Local variable: x = 10 Local variable: y = 40 Local variable: i = 5 Got 1 request: at: Test8.run (Test8.java:6) Local variable: x = 10 Local variable: y = 40 Local variable: i = 5 Got 1 request: at: Test8.run (Test8.java:6) Local variable: x = 10 Local variable: y = 40 Local variable: i = 5 Got 1 request: at: Test8.run (Test8.java:5) Local variable: x = 10 Local variable: y = 50 Local variable: i = 5 Got 1 request: at: Test8.run (Test8.java:5) Local variable: x = 10 Local variable: y = 50 Local variable: i = 6 Got 1 request: at: Test8.run (Test8.java:5) Local variable: x = 10 Local variable: y = 50 Local variable: i = 6 Got 1 request: at: Test8.run (Test8.java:5) Local variable: x = 10 Local variable: y = 50 Local variable: i = 6 Got 1 request: at: Test8.run (Test8.java:5) Local variable: x = 10 Local variable: y = 50 Local variable: i = 6 Got 1 request: at: Test8.run (Test8.java:6) Local variable: x = 10 Local variable: y = 50 Local variable: i = 6 Got 1 request: at: Test8.run (Test8.java:6) Local variable: x = 10 Local variable: y = 50 Local variable: i = 6 Got 1 request: at: Test8.run (Test8.java:6) Local variable: x = 10 Local variable: y = 50 Local variable: i = 6 Got 1 request: at: Test8.run (Test8.java:6) Local variable: x = 10 Local variable: y = 50 Local variable: i = 6 Got 1 request: at: Test8.run (Test8.java:5) Local variable: x = 10 Local variable: y = 60 Local variable: i = 6 Got 1 request: at: Test8.run (Test8.java:5) Local variable: x = 10 Local variable: y = 60 Local variable: i = 7 Got 1 request: at: Test8.run (Test8.java:5) Local variable: x = 10 Local variable: y = 60 Local variable: i = 7 Got 1 request: at: Test8.run (Test8.java:5) Local variable: x = 10 Local variable: y = 60 Local variable: i = 7 Got 1 request: at: Test8.run (Test8.java:5) Local variable: x = 10 Local variable: y = 60 Local variable: i = 7 Got 1 request: at: Test8.run (Test8.java:6) Local variable: x = 10 Local variable: y = 60 Local variable: i = 7 Got 1 request: at: Test8.run (Test8.java:6) Local variable: x = 10 Local variable: y = 60 Local variable: i = 7 Got 1 request: at: Test8.run (Test8.java:6) Local variable: x = 10 Local variable: y = 60 Local variable: i = 7 Got 1 request: at: Test8.run (Test8.java:6) Local variable: x = 10 Local variable: y = 60 Local variable: i = 7 Got 1 request: at: Test8.run (Test8.java:5) Local variable: x = 10 Local variable: y = 70 Local variable: i = 7 Got 1 request: at: Test8.run (Test8.java:5) Local variable: x = 10 Local variable: y = 70 Local variable: i = 8 Got 1 request: at: Test8.run (Test8.java:5) Local variable: x = 10 Local variable: y = 70 Local variable: i = 8 Got 1 request: at: Test8.run (Test8.java:5) Local variable: x = 10 Local variable: y = 70 Local variable: i = 8 Got 1 request: at: Test8.run (Test8.java:5) Local variable: x = 10 Local variable: y = 70 Local variable: i = 8 Got 1 request: at: Test8.run (Test8.java:6) Local variable: x = 10 Local variable: y = 70 Local variable: i = 8 Got 1 request: at: Test8.run (Test8.java:6) Local variable: x = 10 Local variable: y = 70 Local variable: i = 8 Got 1 request: at: Test8.run (Test8.java:6) Local variable: x = 10 Local variable: y = 70 Local variable: i = 8 Got 1 request: at: Test8.run (Test8.java:6) Local variable: x = 10 Local variable: y = 70 Local variable: i = 8 Got 1 request: at: Test8.run (Test8.java:5) Local variable: x = 10 Local variable: y = 80 Local variable: i = 8 Got 1 request: at: Test8.run (Test8.java:5) Local variable: x = 10 Local variable: y = 80 Local variable: i = 9 Got 1 request: at: Test8.run (Test8.java:5) Local variable: x = 10 Local variable: y = 80 Local variable: i = 9 Got 1 request: at: Test8.run (Test8.java:5) Local variable: x = 10 Local variable: y = 80 Local variable: i = 9 Got 1 request: at: Test8.run (Test8.java:5) Local variable: x = 10 Local variable: y = 80 Local variable: i = 9 Got 1 request: at: Test8.run (Test8.java:6) Local variable: x = 10 Local variable: y = 80 Local variable: i = 9 Got 1 request: at: Test8.run (Test8.java:6) Local variable: x = 10 Local variable: y = 80 Local variable: i = 9 Got 1 request: at: Test8.run (Test8.java:6) Local variable: x = 10 Local variable: y = 80 Local variable: i = 9 Got 1 request: at: Test8.run (Test8.java:6) Local variable: x = 10 Local variable: y = 80 Local variable: i = 9 Got 1 request: at: Test8.run (Test8.java:5) Local variable: x = 10 Local variable: y = 90 Local variable: i = 9 Got 1 request: at: Test8.run (Test8.java:5) Local variable: x = 10 Local variable: y = 90 Local variable: i = 10 Got 1 request: at: Test8.run (Test8.java:5) Local variable: x = 10 Local variable: y = 90 Local variable: i = 10 Got 1 request: at: Test8.run (Test8.java:5) Local variable: x = 10 Local variable: y = 90 Local variable: i = 10 Got 1 request: at: Test8.run (Test8.java:5) Local variable: x = 10 Local variable: y = 90 Local variable: i = 10 Got 1 request: at: Test8.run (Test8.java:8) Local variable: x = 10 Local variable: y = 90 Got 1 request: at: Test8.main (Test8.java:19) Local variable: args = instance of java.lang.String[0] (id=8) Got 2 requests: VMDeathEvent Calling exit
10. Repositář se zdrojovými kódy demonstračních příkladů
Zdrojové kódy dnes popsaného demonstračního příkladu JDIStepRequestDemo, testovací třídy Test8 a pomocných skriptů jsou, jak se již v tomto seriálu stalo zvykem, uloženy do Mercurial repositáře dostupného na adrese http://icedtea.classpath.org/people/ptisnovs/jvm-tools/. V následující tabulce najdete odkazy na prozatím nejnovější verze těchto zdrojových kódů:
# | Zdrojový soubor/skript | Umístění souboru v repositáři |
---|---|---|
1 | JDIStepRequestDemo.java | http://icedtea.classpath.org/people/ptisnovs/jvm-tools/file/45f79a75eec0/jdi/JDIStepRequestDemo.java |
2 | compile_JDIStepRequestDemo.sh | http://icedtea.classpath.org/people/ptisnovs/jvm-tools/file/45f79a75eec0/jdi/compile_JDIStepRequestDemo.sh |
3 | Test8.java | http://icedtea.classpath.org/people/ptisnovs/jvm-tools/file/c118e83a0ee7/jdi/Test8.java |
4 | Test8.sh | http://icedtea.classpath.org/people/ptisnovs/jvm-tools/file/c118e83a0ee7/jdi/Test8.sh |
11. Odkazy na Internetu
- Class com.sun.jdi.Bootstrap
http://docs.oracle.com/javase/6/docs/jdk/api/jpda/jdi/com/sun/jdi/package-tree.html - Interface com.sun.jdi.VirtualMachine
http://docs.oracle.com/javase/6/docs/jdk/api/jpda/jdi/com/sun/jdi/VirtualMachine.html - Interface com.sun.jdi.Field
http://docs.oracle.com/javase/6/docs/jdk/api/jpda/jdi/com/sun/jdi/Field.html - Interface com.sun.jdi.ReferenceType
http://docs.oracle.com/javase/6/docs/jdk/api/jpda/jdi/com/sun/jdi/ReferenceType.html - Interface com.sun.jdi.TypeComponent
http://docs.oracle.com/javase/6/docs/jdk/api/jpda/jdi/com/sun/jdi/TypeComponent.html - Interface com.sun.jdi.Accessible
http://docs.oracle.com/javase/6/docs/jdk/api/jpda/jdi/com/sun/jdi/Accessible.html - 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