Obsah
1. Pohled pod kapotu JVM – detekce čtení i zápisu do vybraného atributu s využitím rozhraní JDI
2. Dvě skupiny událostí, které lze využít v rozhraní JDI
3. Metody předepsané v rozhraní AccessWatchpointEvent
4. Metody předepsané v rozhraní ModificationWatchpointEvent
5. Registrace události typu AccessWatchpointEvent
6. Registrace události typu ModificationWatchpointEvent
7. Smyčka událostí se zpracováním jednotlivých typů událostí
8. Výpis zprávy v případě detekce události čtení sledovaného atributu
9. Výpis zprávy v případě detekce události zápisu do sledovaného atributu
10. Informace o lokaci ve zdrojovém kódu, kde došlo ke čtení či k zápisu do sledovaného atributu
11. Zdrojový kód demonstračního příkladu JDIWatchPointDemo3
12. Výstup generovaný demonstračním příkladem JDIWatchPointDemo3
13. Testovací třída Test7 – zápis shodné hodnoty, operátor ++ apod.
14. Výstup generovaný demonstračním příkladem JDIWatchPointDemo3 pro třídu Test7
15. Repositář se zdrojovými kódy demonstračního příkladu
1. Pohled pod kapotu JVM – detekce čtení i zápisu do vybraného atributu s využitím rozhraní JDI
V dnešní části seriálu o programovacím jazyku Java i o virtuálním stroji Javy dokončíme téma, kterému jsme se podrobněji začali věnovat již minule. Jedná se o detekci čtení či zápisu do vybraného atributu určité třídy s využitím rozhraní JDI (Java Debugger Interface), což je funkcionalita implementovaná ve většině debuggerů a dalších ladicích nástrojů. Minule jsme si na dvojici demonstračních příkladů JDIWatchPointDemo1.java a JDIWatchPointDemo2.java ukázali způsob naprogramování reakce na událost typu AccessWatchpointEvent, která nastane ve chvíli, kdy dojde k přístupu ke sledovanému atributu (a to samozřejmě z libovolného vlákna). Slovem „přístup“ zde máme na mysli čtení atributu.
Dnes si pro doplnění popisu této problematiky ukážeme, jakým způsobem lze s využitím rozhraní JDI zjistit, že se změnila hodnota sledovaného atributu, tj. že došlo k operaci zápisu. Jak uvidíme dále, využívá se v tomto případě událost typu ModificationWatchpointEvent, která nám mj. zprostředkuje jak starou, tak i novou hodnotu sledovaného atributu. Díky této vlastnosti události ModificationWatchpointEvent není nutné, aby si debugger či podobná aplikace musela sama složitě udržovat hodnoty všech sledovaných atributů.
Poznámka: změnou atributu je v případě použití JDI myšlen i zápis takové hodnoty, která se shoduje s hodnotou původní.
2. Dvě skupiny událostí, které lze využít v rozhraní JDI
Připomeňme si, že typy událostí podporovaných v JDI lze rozdělit do dvou skupin. V první skupině najdeme události reprezentující změnu stavu cílové JVM jako celku a ve druhé skupině najdeme události, které jsou vztaženy ke konkrétnímu vláknu a ke konkrétnímu místu kódu, kde k události došlo. Do první skupiny náleží tyto typy událostí:
| # | Rozhraní | Popis |
|---|---|---|
| 1 | com.sun.jdi.event.VMStartEvent | Tato událost vznikne těsně před okamžikem, kdy je spuštěno hlavní vlákno aplikace, tj. většinou metoda main(). |
| 2 | com.sun.jdi.event.VMDeathEvent | Tato událost informuje debugger o tom, že cílová JVM bude ukončena. V závislosti na nastavení debuggeru mohou vzniknout dvě události tohoto typu. |
| 3 | com.sun.jdi.event.VMDisconnectEvent | Tato událost informuje debugger o odpojení sledované JVM při jejím ukončování. |
| 4 | com.sun.jdi.event.ClassPrepareEvent | Událost vygenerovaná ve chvíli, kdy se v cílové JVM načítá a připravuje třída. |
| 5 | com.sun.jdi.event.ClassUnloadEvent | Událost vygenerovaná při odstraňování třídy ze sledované JVM (pokud JVM podporuje odstraňování tříd z paměti) |
| 6 | com.sun.jdi.event.ThreadStartEvent | Událost informující debugger o tom, že vzniklo vlákno, které se bude spouštět (událost je vygenerována ještě před vlastním spuštěním vlákna) |
| 7 | com.sun.jdi.event.ThreadDeathEvent | Událost informující debugger o ukončení nějakého vlákna |
Ve druhé skupině typů událostí najdeme všechny události vztažené ke konkrétnímu vláknu a k určitému místu kódu. Tyto typy událostí jsou odvozeny od rozhraní com.sun.jdi.event.LocatableEvent:
Zápis nové hodnoty do sledovaného atributu| # | Rozhraní | Popis |
|---|---|---|
| 1 | com.sun.jdi.event.BreakpointEvent | Vstup na breakpoint. |
| 2 | com.sun.jdi.event.ExceptionEvent | Vznik výjimky (generováno v javovském kódu; pokud výjimka vznikne v nativním kódu, dojde ke vzniku události až později) |
| 3 | com.sun.jdi.event.MethodEntryEvent | Vstup do metody |
| 4 | com.sun.jdi.event.MethodExitEvent | Výskok z metody (libovolný return, ne ovšem při vzniku výjimky – tehdy se generuje jiná událost) |
| 5 | com.sun.jdi.event.WatchpointEvent | Předek následující dvojice rozhraní: |
| 6 | com.sun.jdi.event.AccessWatchpointEvent | Čtení hodnoty sledovaného atributu |
| 7 | com.sun.jdi.event.ModificationWatchpointEvent | |
| 8 | com.sun.jdi.event.MonitorContendedEnteredEvent | Vstup do režimu čekání s právě uvolněným zámkem |
| 9 | com.sun.jdi.event.MonitorContendedEnterEvent | Vstup do režimu čekání se zámkem, který již vlastní jiné vlákno |
| 10 | com.sun.jdi.event.MonitorWaitedEvent | Vlákno ukončilo čekání na synchronizaci (i timeout čekání) |
| 11 | com.sun.jdi.event.MonitorWaitEvent | Vlákno vstoupí do režimu čekání na synchronizaci (monitor) |
| 12 | com.sun.jdi.event.StepEvent | Jeden krok při krokování aplikace |
V dalším textu nás bude z předchozí tabulky zajímat pouze řádek 5, 6 a 7.
3. Metody předepsané v rozhraní AccessWatchpointEvent
V rozhraní com.sun.jdi.event.AccessWatchpointEvent nalezneme metody shodné s metodami, které jsou předepsány v rozhraní com.sun.jdi.event.WatchpointEvent (což je předek):
| # | Návratový typ | Jméno metody | Parametry | Význam |
|---|---|---|---|---|
| 1 | com.sun.jdi.Field | field | () | vrací objekt představující sledovaný atribut |
| 2 | com.sun.jdi.ObjectReference | object | () | vrací objekt obsahující právě čtený či měněný atribut |
| 3 | com.sun.jdi.Value | valueCurrent | () | vrací aktuální hodnotu sledovaného atributu |
| 4 | com.sun.jdi.ThreadReference | thread | () | informace o threadu v monitorované JVM, v němž došlo ke čtení či zápisu do sledovaného atributu |
| 5 | com.sun.jdi.reqeuest.EventRequest | request | () | objekt s jehož pomocí bylo nastaveno sledování tohoto typu události |
| 6 | com.sun.jdi.Location | location | () | lokalizace kódu, který čte sledovaný atribut popř. do tohoto atributu zapisuje |
4. Metody předepsané v rozhraní ModificationWatchpointEvent
V rozhraní com.sun.jdi.event.ModificationWatchpointEvent je oproti rozhraním com.sun.jdi.event.WatchpointEvent i com.sun.jdi.event.AccessWatchpointEvent předepsána jedna zvláštní metoda sloužící k přečtení nové hodnoty zapsané do sledovaného atributu:
| # | Návratový typ | Jméno metody | Parametry | Význam |
|---|---|---|---|---|
| 1 | com.sun.jdi.Field | field | () | vrací objekt představující sledovaný atribut |
| 2 | com.sun.jdi.ObjectReference | object | () | vrací objekt obsahující právě čtený či měněný atribut |
| 3 | com.sun.jdi.Value | valueCurrent | () | vrací aktuální hodnotu sledovaného atributu |
| 4 | com.sun.jdi.ThreadReference | thread | () | informace o threadu v monitorované JVM, v němž došlo ke čtení či zápisu do sledovaného atributu |
| 5 | com.sun.jdi.reqeuest.EventRequest | request | () | objekt s jehož pomocí bylo nastaveno sledování tohoto typu události |
| 6 | com.sun.jdi.Location | location | () | lokalizace kódu, který čte sledovaný atribut popř. do tohoto atributu zapisuje |
| 7 | com.sun.jdi.Value | valueToBe | () | nová hodnota zapisovaná do sledovaného atributu |
5. Registrace události typu AccessWatchpointEvent
V následujícím textu budou postupně popsány ty nejdůležitější metody použité v dnešním demonstračním příkladu JDIWatchPointDemo3. Stejně jako v ostatních demonstračních příkladech, i zde se nejprve provede připojení k již běžícímu virtuálnímu stroji přes socket na portu 6502 a následně se zavolá uživatelská metoda acquireAndUseEventRequestManager, která provede registraci všech potřebných typů událostí a následný vstup do smyčky událostí (event loop):
/**
* 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);
// udalost volana pri pristupu k vybranemu atributu
registerAccessWatchpointRequest(virtualMachine, eventRequestManager);
// udalost volana pri zapisu do vybranemu atributu
registerModificationWatchpointRequest(virtualMachine, eventRequestManager);
// klasicka smycka pro zpracovani udalosti
eventLoop(virtualMachine, vmDeathRequest);
}
V metodě registerAccessWatchpointRequest(), kterou jsme si již ukázali i v předchozí části tohoto seriálu, se nejprve vyžádá generování událostí typu AccessWatchpointEvent a posléze se vytváření těchto typů událostí ještě musí explicitně povolit:
/**
* Registrace udalosti typu AccessWatchpointEvent
* @param virtualMachine
* sledovany virtualni stroj, k nemuz je debugger vzdalene
* pripojen
*/
private static void registerAccessWatchpointRequest(VirtualMachine virtualMachine, EventRequestManager eventRequestManager) {
// ziskani objektu predstavujiciho vybrany atribut
Field field = findField(virtualMachine);
// registrace udalosti
AccessWatchpointRequest accessWatchpointRequest = eventRequestManager.createAccessWatchpointRequest(field);
// po registraci udalosti je jeste nutne tento typ udalosti povolit
accessWatchpointRequest.enable();
}
V této chvíli začnou být do fronty událostí (event queue) zapisovány i události typu AccessWatchpointEvent vyvolané ve chvíli, kdy se čte hodnota sledovaného atributu.
6. Registrace události typu ModificationWatchpointEvent
Dalším typem události, kterou musíme v demonstračním příkladu zpracovávat, jsou události typu ModificationWatchpointEvent, které jsou do fronty událostí vloženy ve chvíli, kdy se v monitorovaném virtuálním stroji provede zápis do sledovaného atributu. O registraci tohoto typu události se stará uživatelská metoda registerModificationWatchpointRequest():
/**
* Registrace udalosti typu ModificationWatchpointEvent
* @param virtualMachine
* sledovany virtualni stroj, k nemuz je debugger vzdalene
* pripojen
*/
private static void registerModificationWatchpointRequest(VirtualMachine virtualMachine, EventRequestManager eventRequestManager) {
// ziskani objektu predstavujiciho vybrany atribut
Field field = findField(virtualMachine);
// registrace udalosti
ModificationWatchpointRequest modificationWatchpointRequest = eventRequestManager.createModificationWatchpointRequest(field);
// po registraci udalosti je jeste nutne tento typ udalosti povolit
modificationWatchpointRequest.enable();
}
7. Smyčka událostí se zpracováním jednotlivých typů událostí
Implementace smyčky událostí je zcela stejná jako smyčka použitá v demonstračních příkladech JDIWatchpointDemo1 a JDIWatchpointDemo2 popsaných minule:
/**
* 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 :)
}
}
Vlastní čtení událostí z fronty řeší uživatelská metoda processEvent(), v níž se zpracovávají čtyři typy událostí – AccessWatchpointEvent, ModificationWatchpointEvent, VMStartEvent a VMDeathEvent. V této metodě je nutné jednotlivé události rozpoznávat s využitím operátoru instanceof (druhou možností by bylo porovnání názvů tříd, to však není příliš efektivní):
/**
* 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 AccessWatchpointEvent) {
// podrobnejsi vypis o pristupu k atributu
printAccessWatchpointInfo((AccessWatchpointEvent)event);
}
else if (event instanceof ModificationWatchpointEvent) {
// podrobnejsi vypis o zapisu do atributu
printModificationWatchpointInfo((ModificationWatchpointEvent)event);
}
else {
System.out.println(" other event");
}
}
// znovu postit vsechna vlakna
eventSet.resume();
}
catch (InterruptedException e) {
e.printStackTrace();
}
return true;
}
8. Výpis zprávy v případě detekce události čtení sledovaného atributu
Konečně se dostáváme k metodám našeho demonstračního příkladu, které jsou zavolány ve chvíli čtení popř. zápisu do sledovaného atributu. První metodou je printAccessWatchpointInfo(), která vytiskne hodnotu atributu ve chvíli jeho čtení nějakým vláknem ve sledovaném virtuálním stroji Javy. Oproti podobné metodě popsané minule se však zde hodnota atributu získává poněkud odlišným způsobem. Připomeňme si, že minule jsme pro přečtení hodnoty celočíselného atributu používali zvláštní metodu nazvanou getFieldValue(), která vypadala následovně:
/**
* Ziskani hodnoty vybraneho atributu.
*
* @param event
* udalost typu AccessWatchpointEvent
*/
private static int getFieldValue(AccessWatchpointEvent event) {
ObjectReference objectReference = event.object();
// dopredu zname typ atributu, tudiz lze provest pretypovani
IntegerValue value = (IntegerValue)objectReference.getValue(event.field());
return value.intValue();
}
Dnes naproti tomu pro ilustraci poměrně velké variability rozhraní JDI použijeme jinou formu této metody, v níž se využije metoda WatchPointEvent.valueCurrent():
/**
* Ziskani hodnoty vybraneho (sledovaneho) atributu.
*
* @param event
* udalost typu WatchpointEvent
* @return hodnota, ktera je ulozena prirazena ve sledovanem atributu
* prevedena na retezec
*/
private static String getFieldValue(WatchpointEvent event) {
return event.valueCurrent().toString();
}
Při vzniku události typu AccessWatchpointEvent se získá jméno a aktuální hodnota sledovaného atributu a posléze se tyto hodnoty vypíšou na standardní výstup. Za těmito hodnotami se navíc ještě vypíšou další informace s využitím uživatelské metody printOtherWatchPointInfo() popsané v dalším textu:
/**
* Zprava vypsana ve chvili, kdy se cte hodnota sledovaneho atributu.
*
* @param event
* udalost typu AccessWatchpointEvent
*/
private static void printAccessWatchpointInfo(AccessWatchpointEvent event) {
// ziskat aktualni hodnotu atributu
String fieldValue = getFieldValue(event);
String fieldName = event.field().name();
System.out.format(" read value %s from attribute %s ", fieldValue, fieldName);
printOtherWatchPointInfo(event);
}
9. Výpis zprávy v případě detekce události zápisu do sledovaného atributu
Další uživatelská metoda printModificationWatchpointInfo() je zavolána tehdy, pokud ve sledovaném virtuálním stroji Javy dojde k zápisu do sledovaného atributu:
/**
* Zprava vypsana ve chvili, kdy se zapisuje nova hodnota do sledovaneho atributu.
*
* @param event
* udalost typu ModificationWatchpointEvent
*/
private static void printModificationWatchpointInfo(ModificationWatchpointEvent event) {
// ziskat aktualni hodnotu atributu
String fieldValue = getFieldValue(event);
String fieldName = event.field().name();
String newValue = getNewValue(event);
System.out.format(" overwrite value %s in attribute %s by new value %s", fieldValue, fieldName, newValue);
printOtherWatchPointInfo(event);
}
Od uživatelské metody printAccessWatchpointInfo() se metoda printModificationWatchpointInfo() liší pouze tím, že přibyla informace o nové hodnotě atributu, což je informace získaná z další pomocné uživatelské metody nazvané getNewValue():
/**
* Precteni nove hodnoty, ktera ma byt prirazena do sledovaneho atributu.
*
* @param event
* udalost typu ModificationWatchpointEvent
* @return nova hodnota, ktera ma byt prirazena do sledovaneho atributu
* prevedena na retezec
*/
private static String getNewValue(ModificationWatchpointEvent event) {
return event.valueToBe().toString();
}
10. Informace o lokaci ve zdrojovém kódu, kde došlo ke čtení či k zápisu do sledovaného atributu
V uživatelských metodách printAccessWatchpointInfo() i printModificationWatchpointInfo() je nutné mj. vypsat i informaci o lokaci ve zdrojovém kódu, v němž došlo ke čtení či k zápisu do sledovaného atributu. O tuto společnou funkci se stará metoda printOtherWatchPointInfo(), která nejprve z objektu typu WatchpointEvent získá instanci třídy Location a posléze z této instance přečte jméno třídy i metody, název zdrojového kódu, v němž byla třída deklarována a konečně i číslo řádku, na němž došlo ke čtení či zápisu do sledovaného atributu (poslední zmiňovaná informace se však vypíše pouze tehdy, pokud je testovací třída přeložena s přepínačem -g):
/**
* Vypis lokace v kodu, kde dochazi ke cteni ci k zapisu nove hodnoty
* do sledovaneho atributu.
*
* @param event
* udalost typu WatchpointEvent
*/
private static void printOtherWatchPointInfo(WatchpointEvent event) {
// ziskat objekt typu Location, ktery obsahuje informaci o tom,
// ve kterem miste kodu se k atributu pristupovalo
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, v niz se k atributu pristupuje
System.out.format("(%s:%s)\n", sourceName, lineNumber);
}
11. Zdrojový kód demonstračního příkladu JDIWatchPointDemo3
Celý dnešní demonstrační příklad JDIWatchPointDemo3 pracuje tak, že se nejdříve zaregistrují všechny typy zpracovávaných událostí a posléze se spustí smyčka událostí. Vytváření všech sledovaných typů událostí nastane ve chvíli, kdy aplikace běžící v monitorovaném (cílovém) virtuálním stroji Javy přečte (v libovolném vláknu) atribut s plným jménem Test6.testedField, popř. modifikuje hodnotu tohoto atributu. Ve chvíli, kdy k přečtení tohoto atributu dojde, může demonstrační příklad ze své fronty událostí získat objekt typu AccessWatchpointEvent a vypsat prozatím základní informaci o této události na standardní výstup. Podobně je tomu i při zápisu do atributu, tj. při zpracování události typu ModificationWatchpointEvent. Následuje výpis celého zdrojového kódu dnešního prvního demonstračního příkladu:
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.Field;
import com.sun.jdi.Location;
import com.sun.jdi.ReferenceType;
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.AccessWatchpointEvent;
import com.sun.jdi.event.Event;
import com.sun.jdi.event.EventQueue;
import com.sun.jdi.event.EventSet;
import com.sun.jdi.event.ModificationWatchpointEvent;
import com.sun.jdi.event.VMDeathEvent;
import com.sun.jdi.event.VMStartEvent;
import com.sun.jdi.event.WatchpointEvent;
import com.sun.jdi.request.AccessWatchpointRequest;
import com.sun.jdi.request.EventRequestManager;
import com.sun.jdi.request.ModificationWatchpointRequest;
import com.sun.jdi.request.VMDeathRequest;
/**
* Demonstracni priklad JDIWatchPointDemo3, v nemz je ukazan zpusob
* vypisu zprav pri cteni i pri zapisu do sledovaneho atributu vybrane tridy.
*
* 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 JDIWatchPointDemo3 {
/**
* 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 = "Test6";
/**
* Jmeno sledovaneho atributu v testovane tride.
*/
private static final String TESTED_FIELD_NAME = "testedField";
/**
* Vstupni metoda 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
*/
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.
* @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);
// udalost volana pri pristupu k vybranemu atributu
registerAccessWatchpointRequest(virtualMachine, eventRequestManager);
// udalost volana pri zapisu do vybranemu atributu
registerModificationWatchpointRequest(virtualMachine, eventRequestManager);
// klasicka smycka pro zpracovani udalosti
eventLoop(virtualMachine, vmDeathRequest);
}
/**
* Registrace udalosti typu VMDeathEvent
*/
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 AccessWatchpointEvent
* @param virtualMachine
* sledovany virtualni stroj, k nemuz je debugger vzdalene
* pripojen
*/
private static void registerAccessWatchpointRequest(VirtualMachine virtualMachine, EventRequestManager eventRequestManager) {
// ziskani objektu predstavujiciho vybrany atribut
Field field = findField(virtualMachine);
// registrace udalosti
AccessWatchpointRequest accessWatchpointRequest = eventRequestManager.createAccessWatchpointRequest(field);
// po registraci udalosti je jeste nutne tento typ udalosti povolit
accessWatchpointRequest.enable();
}
/**
* Registrace udalosti typu ModificationWatchpointEvent
* @param virtualMachine
* sledovany virtualni stroj, k nemuz je debugger vzdalene
* pripojen
*/
private static void registerModificationWatchpointRequest(VirtualMachine virtualMachine, EventRequestManager eventRequestManager) {
// ziskani objektu predstavujiciho vybrany atribut
Field field = findField(virtualMachine);
// registrace udalosti
ModificationWatchpointRequest modificationWatchpointRequest = eventRequestManager.createModificationWatchpointRequest(field);
// po registraci udalosti je jeste nutne tento typ udalosti povolit
modificationWatchpointRequest.enable();
}
/**
* Ziskani objektu predstavujiciho vybrany atribut ve sledovane VM.
*
* @param virtualMachine
* sledovany virtualni stroj, k nemuz je debugger vzdalene
* pripojen
* @return nalezeny atribut
*/
private static Field findField(VirtualMachine virtualMachine) {
// mela by se vratit jedina trida
List<ReferenceType> allClasses = virtualMachine.classesByName(TEST_CLASS_NAME);
System.out.println("Number of classes with name '" + TEST_CLASS_NAME + "': " + allClasses.size());
// vratila se jedina trida, takze ji precteme
ReferenceType class1 = allClasses.get(0);
// ziskani atributu z teto tridy
return class1.fieldByName(TESTED_FIELD_NAME);
}
/**
* 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 AccessWatchpointEvent) {
// podrobnejsi vypis o pristupu k atributu
printAccessWatchpointInfo((AccessWatchpointEvent)event);
}
else if (event instanceof ModificationWatchpointEvent) {
// podrobnejsi vypis o zapisu do atributu
printModificationWatchpointInfo((ModificationWatchpointEvent)event);
}
else {
System.out.println(" other event");
}
}
// znovu postit vsechna vlakna
eventSet.resume();
}
catch (InterruptedException e) {
e.printStackTrace();
}
return true;
}
/**
* Zprava vypsana ve chvili, kdy se cte hodnota sledovaneho atributu.
*
* @param event
* udalost typu AccessWatchpointEvent
*/
private static void printAccessWatchpointInfo(AccessWatchpointEvent event) {
// ziskat aktualni hodnotu atributu
String fieldValue = getFieldValue(event);
String fieldName = event.field().name();
System.out.format(" read value %s from attribute %s ", fieldValue, fieldName);
printOtherWatchPointInfo(event);
}
/**
* Zprava vypsana ve chvili, kdy se zapisuje nova hodnota do sledovaneho atributu.
*
* @param event
* udalost typu ModificationWatchpointEvent
*/
private static void printModificationWatchpointInfo(ModificationWatchpointEvent event) {
// ziskat aktualni hodnotu atributu
String fieldValue = getFieldValue(event);
String fieldName = event.field().name();
String newValue = getNewValue(event);
System.out.format(" overwrite value %s in attribute %s by new value %s", fieldValue, fieldName, newValue);
printOtherWatchPointInfo(event);
}
/**
* Vypis lokace v kodu, kde dochazi ke cteni ci k zapisu nove hodnoty
* do sledovaneho atributu.
*
* @param event
* udalost typu WatchpointEvent
*/
private static void printOtherWatchPointInfo(WatchpointEvent event) {
// ziskat objekt typu Location, ktery obsahuje informaci o tom,
// ve kterem miste kodu se k atributu pristupovalo
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, v niz se k atributu pristupuje
System.out.format("(%s:%s)\n", sourceName, lineNumber);
}
/**
* Ziskani hodnoty vybraneho (sledovaneho) atributu.
*
* @param event
* udalost typu WatchpointEvent
* @return hodnota, ktera je ulozena prirazena ve sledovanem atributu
* prevedena na retezec
*/
private static String getFieldValue(WatchpointEvent event) {
return event.valueCurrent().toString();
}
/**
* Precteni nove hodnoty, ktera ma byt prirazena do sledovaneho atributu.
*
* @param event
* udalost typu ModificationWatchpointEvent
* @return nova hodnota, ktera ma byt prirazena do sledovaneho atributu
* prevedena na retezec
*/
private static String getNewValue(ModificationWatchpointEvent event) {
return event.valueToBe().toString();
}
/**
* 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>";
}
}
12. Výstup generovaný demonstračním příkladem JDIWatchPointDemo3
Podívejme se nyní na způsob přeložení a následného použití demonstračního příkladu JDIWatchPointDemo3. Překlad se provede následujícím skriptem (který předpokládá instalaci OpenJDK 6 do nastaveného adresáře):
javac -classpath /usr/lib/jvm/java-6-openjdk/lib/tools.jar:. JDIWatchpointDemo3.java
Před spuštěním tohoto příkladu je nutné z jiného terminálu spustit i testovací aplikaci Test6 (její zdrojový kód jsme si ukazovali minule):
java -agentlib:jdwp=transport=dt_socket,server=y,address=6502,suspend=y Test6
Spuštění dnešního demonstračního příkladu zajistí následující příkaz:
java -cp /usr/lib/jvm/java-6-openjdk/lib/tools.jar:. JDIWatchpointDemo3
Jakmile se testovací aplikace Test6 spustí, otevře její virtuální stroj Javy port číslo 6502, na němž se očekává připojení debuggeru. Následně tato aplikace počká na stisk libovolné klávesy. Po připojení debuggeru (resp. našeho demonstračního příkladu JDIWatchpointDemo3) by se na standardní výstup měly vypsat následující zprávy:
Connecting to virtual machine
Connected
Number of classes with name 'Test6': 1
Number of classes with name 'Test6': 1
Got 1 request:
overwrite value 0 in attribute testedField by new value 1 at: Test6. (Test6.java:3)
Got 1 request:
read value 1 from attribute testedField at: Test6.run (Test6.java:6)
Got 1 request:
overwrite value 1 in attribute testedField by new value 42 at: Test6.run (Test6.java:6)
Got 1 request:
overwrite value 42 in attribute testedField by new value 1 at: Test6.foo (Test6.java:12)
Got 1 request:
read value 1 from attribute testedField at: Test6.bar (Test6.java:18)
Got 1 request:
overwrite value 1 in attribute testedField by new value 2 at: Test6.foo (Test6.java:12)
Got 1 request:
read value 2 from attribute testedField at: Test6.bar (Test6.java:18)
Got 1 request:
overwrite value 2 in attribute testedField by new value 4 at: Test6.foo (Test6.java:12)
Got 1 request:
read value 4 from attribute testedField at: Test6.bar (Test6.java:18)
Got 1 request:
overwrite value 4 in attribute testedField by new value 8 at: Test6.foo (Test6.java:12)
Got 1 request:
read value 8 from attribute testedField at: Test6.bar (Test6.java:18)
Got 1 request:
overwrite value 8 in attribute testedField by new value 16 at: Test6.foo (Test6.java:12)
Got 1 request:
read value 16 from attribute testedField at: Test6.bar (Test6.java:18)
Got 1 request:
overwrite value 16 in attribute testedField by new value 32 at: Test6.foo (Test6.java:12)
Got 1 request:
read value 32 from attribute testedField at: Test6.bar (Test6.java:18)
Got 1 request:
overwrite value 32 in attribute testedField by new value 64 at: Test6.foo (Test6.java:12)
Got 1 request:
read value 64 from attribute testedField at: Test6.bar (Test6.java:18)
Got 1 request:
overwrite value 64 in attribute testedField by new value 128 at: Test6.foo (Test6.java:12)
Got 1 request:
read value 128 from attribute testedField at: Test6.bar (Test6.java:18)
Got 1 request:
overwrite value 128 in attribute testedField by new value 256 at: Test6.foo (Test6.java:12)
Got 1 request:
read value 256 from attribute testedField at: Test6.bar (Test6.java:18)
Got 1 request:
overwrite value 256 in attribute testedField by new value 512 at: Test6.foo (Test6.java:12)
Got 1 request:
read value 512 from attribute testedField at: Test6.bar (Test6.java:18)
Got 2 requests:
VMDeathEvent
Calling exit
Povšimněte si zejména způsobu výpisu zpráv v případě, že se na jednom programovém řádku čte aktuální hodnota atributu a současně se zapisuje hodnota nová. Jedná se především o řádek Test6.java:6, který vypadá následovně:
this.testedField *= value;
Podobný význam (současné čtení i zápis nové hodnoty do sledovaného atributu) by měl například i podobný řádek:
this.testedField ++;
13. Testovací třída Test7 – zápis shodné hodnoty, operátor ++ apod.
Zkusme si nyní dnešní demonstrační příklad JDIWatchpointDemo3 vyzkoušet spustit proti nové testovací třídě Test7.java, kde se atribut čte či zapisuje v trojici metod. Povšimněte si, že v první metodě foo() se stále zapisuje stejná konstanta, ve druhé metodě bar() se přičítá nula (tudíž se hodnota atributu nemění, ovšem zápis se provádí) a ve třetí metodě baz() se používá operátor ++ pro čtení i zápis:
public class Test7 {
int testedField = 1;
public void run() {
foo();
bar();
baz();
}
public void foo() {
for (int i = 1; i < 10; i++) {
this.testedField = 0;
}
}
public void bar() {
for (int i = 1; i < 10; i++) {
this.testedField += 0;
}
}
public void baz() {
for (int i = 1; i < 10; i++) {
this.testedField++;
}
}
public static void main(String[] args) {
System.out.println("Press any key");
try {
System.in.read();
}
catch (java.io.IOException e) {
// . //
}
new Test7().run();
}
}
14. Výstup generovaný demonstračním příkladem JDIWatchPointDemo3 pro třídu Test7
Podívejme se nyní na výstup příkladu JDIWatchPointDemo3 ve chvíli, kdy je spuštěn oproti třídě Test7 v monitorovaném virtuálním stroji Javy:
Connecting to virtual machine
Connected
Number of classes with name 'Test7': 1
Test7.testedField
Number of classes with name 'Test7': 1
Test7.testedField
Got 1 request:
overwrite value 0 in attribute testedField by new value 1 at: Test7. (Test7.java:3)
Got 1 request:
overwrite value 1 in attribute testedField by new value 0 at: Test7.foo (Test7.java:13)
Got 1 request:
overwrite value 0 in attribute testedField by new value 0 at: Test7.foo (Test7.java:13)
Got 1 request:
overwrite value 0 in attribute testedField by new value 0 at: Test7.foo (Test7.java:13)
Got 1 request:
overwrite value 0 in attribute testedField by new value 0 at: Test7.foo (Test7.java:13)
Got 1 request:
overwrite value 0 in attribute testedField by new value 0 at: Test7.foo (Test7.java:13)
Got 1 request:
overwrite value 0 in attribute testedField by new value 0 at: Test7.foo (Test7.java:13)
Got 1 request:
overwrite value 0 in attribute testedField by new value 0 at: Test7.foo (Test7.java:13)
Got 1 request:
overwrite value 0 in attribute testedField by new value 0 at: Test7.foo (Test7.java:13)
Got 1 request:
overwrite value 0 in attribute testedField by new value 0 at: Test7.foo (Test7.java:13)
Got 1 request:
read value 0 from attribute testedField at: Test7.bar (Test7.java:19)
Got 1 request:
overwrite value 0 in attribute testedField by new value 0 at: Test7.bar (Test7.java:19)
Got 1 request:
read value 0 from attribute testedField at: Test7.bar (Test7.java:19)
Got 1 request:
overwrite value 0 in attribute testedField by new value 0 at: Test7.bar (Test7.java:19)
Got 1 request:
read value 0 from attribute testedField at: Test7.bar (Test7.java:19)
Got 1 request:
overwrite value 0 in attribute testedField by new value 0 at: Test7.bar (Test7.java:19)
Got 1 request:
read value 0 from attribute testedField at: Test7.bar (Test7.java:19)
Got 1 request:
overwrite value 0 in attribute testedField by new value 0 at: Test7.bar (Test7.java:19)
Got 1 request:
read value 0 from attribute testedField at: Test7.bar (Test7.java:19)
Got 1 request:
overwrite value 0 in attribute testedField by new value 0 at: Test7.bar (Test7.java:19)
Got 1 request:
read value 0 from attribute testedField at: Test7.bar (Test7.java:19)
Got 1 request:
overwrite value 0 in attribute testedField by new value 0 at: Test7.bar (Test7.java:19)
Got 1 request:
read value 0 from attribute testedField at: Test7.bar (Test7.java:19)
Got 1 request:
overwrite value 0 in attribute testedField by new value 0 at: Test7.bar (Test7.java:19)
Got 1 request:
read value 0 from attribute testedField at: Test7.bar (Test7.java:19)
Got 1 request:
overwrite value 0 in attribute testedField by new value 0 at: Test7.bar (Test7.java:19)
Got 1 request:
read value 0 from attribute testedField at: Test7.bar (Test7.java:19)
Got 1 request:
overwrite value 0 in attribute testedField by new value 0 at: Test7.bar (Test7.java:19)
Got 1 request:
read value 0 from attribute testedField at: Test7.baz (Test7.java:25)
Got 1 request:
overwrite value 0 in attribute testedField by new value 1 at: Test7.baz (Test7.java:25)
Got 1 request:
read value 1 from attribute testedField at: Test7.baz (Test7.java:25)
Got 1 request:
overwrite value 1 in attribute testedField by new value 2 at: Test7.baz (Test7.java:25)
Got 1 request:
read value 2 from attribute testedField at: Test7.baz (Test7.java:25)
Got 1 request:
overwrite value 2 in attribute testedField by new value 3 at: Test7.baz (Test7.java:25)
Got 1 request:
read value 3 from attribute testedField at: Test7.baz (Test7.java:25)
Got 1 request:
overwrite value 3 in attribute testedField by new value 4 at: Test7.baz (Test7.java:25)
Got 1 request:
read value 4 from attribute testedField at: Test7.baz (Test7.java:25)
Got 1 request:
overwrite value 4 in attribute testedField by new value 5 at: Test7.baz (Test7.java:25)
Got 1 request:
read value 5 from attribute testedField at: Test7.baz (Test7.java:25)
Got 1 request:
overwrite value 5 in attribute testedField by new value 6 at: Test7.baz (Test7.java:25)
Got 1 request:
read value 6 from attribute testedField at: Test7.baz (Test7.java:25)
Got 1 request:
overwrite value 6 in attribute testedField by new value 7 at: Test7.baz (Test7.java:25)
Got 1 request:
read value 7 from attribute testedField at: Test7.baz (Test7.java:25)
Got 1 request:
overwrite value 7 in attribute testedField by new value 8 at: Test7.baz (Test7.java:25)
Got 1 request:
read value 8 from attribute testedField at: Test7.baz (Test7.java:25)
Got 1 request:
overwrite value 8 in attribute testedField by new value 9 at: Test7.baz (Test7.java:25)
Got 2 requests:
VMDeathEvent
Calling exit
15. Repositář se zdrojovými kódy demonstračního příkladu
Zdrojové kódy demonstračního příkladu JDIWatchPointDemo3.java jsou společně s dalšími pomocnými soubory 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 | JDIWatchPointDemo3.java | http://icedtea.classpath.org/people/ptisnovs/jvm-tools/file/e48b9b41a61c/jdi/JDIWatchPointDemo3.java |
| 2 | compile_JDIWatchpointDemo3.sh | http://icedtea.classpath.org/people/ptisnovs/jvm-tools/file/e48b9b41a61c/jdi/compile_JDIWatchpointDemo3.sh |
| 3 | Test6.java | http://icedtea.classpath.org/people/ptisnovs/jvm-tools/file/64baaf2ff0df/jdi/Test6.java |
| 4 | Test6.sh | http://icedtea.classpath.org/people/ptisnovs/jvm-tools/file/64baaf2ff0df/jdi/Test6.sh |
| 5 | Test7.java | http://icedtea.classpath.org/people/ptisnovs/jvm-tools/file/e48b9b41a61c/jdi/Test7.java |
| 6 | Test7.sh | http://icedtea.classpath.org/people/ptisnovs/jvm-tools/file/e48b9b41a61c/jdi/Test7.sh/a> |
16. 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